三分鐘掌握共享記憶體 & Actor並發模型
吃點好的,很有必要。今天介紹常見的兩種並發模型: 共享記憶體&Actor
共享記憶體
面向對象編程中,萬物都是對象,數據+行為=對象;
多核時代,可並行多個執行緒,但是受限於資源對象,執行緒之間存在對共享記憶體的搶佔/等待,實質是多執行緒調用對象的行為方法,這涉及#執行緒安全#執行緒同步#。
假如現在有一個任務,找100000以內的素數的個數,如果用共享記憶體的方法,程式碼如下:
可以看到,這些執行緒共享了sum
變數,對sum
做sum++
操作時必須上鎖。
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模型解耦了調用關係,天然適合分散式場景。
總結陳詞
- 何為「並發模型」,模型是達成某個方案的編程風格,共享記憶體/Actor並發模型說不上孰優孰劣,適用場景有偏向。
- 共享記憶體並發模型,更強調多執行緒對於資源的掌控力。
- 從概念上得知,Actor模型強調消息觸發,更適合分散式場景,解耦了調用方和提供方(我這裡演示的TPL Dataflow是進程內Actor模型)。
- Golang使用的
Channel
是類Actor模型
,使用Channel進一步解耦了調用的參與方,你都不用關注下游提供者是誰。
作為一名編程老兵,深知大家平時常用的是共享記憶體並發模型,開口閉口「多執行緒」,「鎖」,
可能很多人並沒有關注到Actor模型,微軟進程內Actor TPL Dataflow香氣側漏,值得推薦。
多對比、多體驗不同的編程風格,別有洞天。