三分钟掌握共享内存 & Actor并发模型

简介: 吃点好的,很有必要。今天介绍常见的两种并发模型:共享内存&Actor

共享内存


面向对象编程中,万物都是对象,数据+行为=对象


多核时代,可并行多个线程,但是受限于资源对象,线程之间存在对共享内存的抢占/等待,实质是多线程调用对象的行为方法,这涉及#线程安全#线程同步#。

8b16f6d46e9f3af12be415a4e5252b80.png


假如现在有一个任务,找100000以内的素数的个数,如果用共享内存的方法,代码如下:


可以看到,这些线程共享了sum变量,对sumsum++操作时必须上锁。


using System;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
namespace Paralleler
{
    class Program
    {
        static object syncObj = new object();
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ShareMemory();
            sw.Stop();
            Console.WriteLine($"共享内存并发模型耗时:{sw.Elapsed}");
        }
        static void ShareMemory()
        {
            var sum = 0;
            Parallel.For(1, 100000 + 1,(x, state) =>
            {
                var f = true;
                if (x == 1)
                    f = false;
                for (int i = 2; i <= x / 2; i++)
                {
                    if (x % i == 0)  // 被[2,x/2]任一数字整除,就不是质数
                        f = false;
                }
                if(f== true)
                {
                    lock(syncObj)
                    {
                        sum++;   // 共享了sum对象,“++”就是调用sum对象的成员方法
                    }
                }
            });
            Console.WriteLine($"1-100000内质数的个数是{sum}");
        }
    }
}


共享内存更贴合"面向对象开发者的固定思维", 强调线程对于资源的掌控力。


Actor模型


Actor模型则认为一切皆是Actorshare nothing Actor模型内部的状态由自己的行为维护,外部线程不能直接调对象的行为,必须通过消息才能激发行为,也就是消息传递机制来代替共享内存模型对成员方法的调用, 这样保证Actor内部数据只能被自己修改


Actor模型= 数据+行为+消息。

6f533386212d3c89b6fe6dedd1fe8c10.png


还是找到100000内的素数,我们使用.NET TPL Dataflow来完成,代码如下:


每个Actor的产出物就是流转到下一个Actor的消息。


using System;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
using System.Diagnostics;
namespace Paralleler
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Actor();
            sw.Stop();
            Console.WriteLine($"Actor并发模型耗时:{sw.Elapsed}");  
        }
        static void Actor()
        {
            var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
            var transfromBlock = new TransformBlock<int,bool>(x=> 
            {
                var f = true;
                if (x == 1)
                    f = false;
                for (int i = 2; i <= x / 2; i++)
                {
                    if (x % i == 0)  // 被[2,x/2]任一数字整除,就不是质数
                        f = false;
                }
                return f;
            }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism =50 });
            var sum = 0;
            var actionBlock = new ActionBlock<bool>(x=>
            {
                if (x == true)
                    sum++;
            },new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1   });//这里必须使用默认并发度
            transfromBlock.LinkTo(actionBlock, linkOptions);
            // 准备从pipeline头部开始投递
            for (int i = 1; i <= 100000; i++)
            {
                transfromBlock.Post(i);
            }
            transfromBlock.Complete();  // 通知头部,不再投递了; 会将信息传递到下游。
            actionBlock.Completion.Wait();  // 等待尾部执行完成
            Console.WriteLine($"1-100000内质数的个数是{sum}");
        }
    }
}


Actor并发模型强调的是消息触发。


还不过瘾


共享内存模型:其实是并行线程调用对象的成员方法,这里不可避免存在加锁/解锁, 需要开发者自行关注线程同步、线程安全。


Actor模型以流水线管道的形式,各Actor独立挨个处理各自专属业务,等待消息流入。


我也很容易推断,每个Actor的伪代码实现:存在循环,不断处理新流入的消息。


var queue= new Queue(1000); 
 for{
    if(queue.Dequeue() != null) {
       // Done bussiness 
    }
    Thread.Sleep(50ms);
 }


所以Actor模型,开发者不用关注线程锁,同时,Actor模型解耦了调用关系,天然适合分布式场景。


ebba5ab963368d1e6c798f632f319c0a.png


总结陈词


1.何为“并发模型”,模型是达成某个方案的编程风格,共享内存/Actor并发模型说不上孰优孰劣,适用场景有偏向。


2.共享内存并发模型,更强调多线程对于资源的掌控力。


3.从概念上得知,Actor模型强调消息触发,更适合分布式场景,解耦了调用方和提供方(我这里演示的TPL Dataflow是进程内Actor模型)  


  TPL Dataflow组件应对高并发,低延迟要求


4.Golang使用的Channel类Actor模型,使用Channel进一步解耦了调用的参与方,你都不用关注下游提供者是谁。 Waiting For Next Article......


作为一名编程老兵,深知大家平时常用的是共享内存并发模型,开口闭口“多线程”,“锁”, 可能很多人并没有关注到Actor模型,微软进程内Actor TPL Dataflow香气侧漏,值得推荐。


多对比、多体验不同的编程风格,别有洞天。

相关文章
|
SQL 开发框架 .NET
共享内存 & Actor并发模型到底哪个快?
请大家仔细对比结论和下图,脱离场景和硬件环境谈性能就是耍流氓,理解不同并发模型的风格和能力是关键, 针对场景和未来的拓展性、可维护性、可操作性做技术选型 。
共享内存 & Actor并发模型到底哪个快?
|
4月前
|
存储 分布式计算 Hadoop
HadoopCPU、内存、存储限制
【7月更文挑战第13天】
275 14
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
337 0
|
15天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
29 1
|
19天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
23天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
28天前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
37 4
|
27天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
48 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
存储 机器学习/深度学习 人工智能
数据在内存中的存储
数据在内存中的存储
|
29天前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储