三分鐘掌握共享內存 & 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 });
           
            var sum = 0;
            var actionBlock = new ActionBlock<bool>(x=>
            {
                if (x == true)
                    sum++;
            },new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 });
            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}");
        }
    }
}

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香氣側漏,值得推薦。

多對比、多體驗不同的編程風格,別有洞天。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章