zoukankan      html  css  js  c++  java
  • 三分钟掌握共享内存 & Actor并发模型

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

    共享内存

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

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

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

    using System;
    using System.Threading.Tasks;
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Diagnostics;
    
    /// <summary>
    /// 利用并行编程库Parallel,计算10000内素数的个数
    /// </summary>
    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-10000内质数的个数是{sum}");
            }
        }
    }
    

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

    Actor模型

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

    还是找到10000内的素数,我们使用.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;
    
    /// <summary>
    /// 利用并行编程库Paralleler,计算10000内素数的个数
    /// </summary>
    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 bufferBlock = new BufferBlock<int>();
                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 });  // 这里因为不涉及外部共享变量,可以并发线程处理。默认MaxDegreeOfParallelism =1, 表示挨个处理。
               
                var sum = 0;
                var actionBlock = new ActionBlock<bool>(x=>
                {
                    if (x == true)
                        sum++;
                },new ExecutionDataflowBlockOptions {  });      // 这里因为涉及外部共享变量sum, 不能加锁的话,就挨个处理,使用默认并发度=1
                transfromBlock.LinkTo(actionBlock, linkOptions);
                // 准备从pipeline头部开始投递
                for (int i = 1; i <= 100000; i++)
                {
                    transfromBlock.Post(i);
                }
                transfromBlock.Complete();  // 通知头部,不再投递了; 会将信息传递到下游。
                actionBlock.Completion.Wait();  // 等待尾部执行完成
                Console.WriteLine($"1-10000内质数的个数是{sum}");
            }
        }
    }
    

    与TPL中的原语不同,TPL datflow中的所有块默认是单线程的。并发度MaxDegreeOfParallelism 默认为1;意味着 TransformBlock 和ActionBlock都是一个线程挨个处理。

    在计算是否为素数时,可以在block开启多线程并发,因为不涉及外部共享变量。
    在累加时,涉及外部共享变量,不加锁,就得使用默认并发度1。

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

    还不过瘾

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

    Actor模型:以流水线管道的形式,各Actor独立处理各自专属业务,等待消息流入,我也很容易推断,每个Actor的实现过程:存在循环,不断处理新流入的消息。

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

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

    总结陈词

    1. 何为“并发模型”,模型是达成某个方案的编程风格,共享内存/Actor并发模型说不上孰优孰劣,适用场景有偏向。
    2. 共享内存并发模型,更强调多线程对于资源的掌控力。
    3. 从概念上得知,Actor模型强调消息触发,更适合分布式场景,解耦了调用方和提供方(我这里演示的TPL Dataflow是进程内Actor模型)。
    4. Golang使用的Channel类Actor模型,使用Channel进一步解耦了调用的参与方,你都不用关注下游提供者是谁。

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

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


    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15035283.html

    欢迎关注我的原创高价值公众号

    上海鲜花港 - 郁金香
  • 相关阅读:
    思维方法
    设计模式之创建者模式
    舍本逐末 事倍功半
    凡事预则立,不预则废
    股票投资,你一定要知道的运气三定律
    具体问题具体分析
    实事求是
    炒股与运气
    判大势定策略 寻找最适合的类型股
    炒股的本质-规避风险,增大收益-按客观规律办事
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15035283.html
Copyright © 2011-2022 走看看