三分鐘掌握Actor和CSP模型
go的CSP模型
- 傳統(tǒng)多線程的的共享內(nèi)存(ShareMemory)模型使用lock,condition等同步原語來強行規(guī)定進程的執(zhí)行順序。
- Actor模型,是基于消息傳遞的并發(fā)模型, 強調(diào)的是Actor這個工作實體,每個Actor自行決定消息傳遞的方向(要傳遞的ActorB),通過消息傳遞形成流水線。
本文現(xiàn)在要記錄的是另一種基于消息傳遞的并發(fā)模型:CSP(communicating sequential process順序通信過程)。
在CSP模型,worker之間不直接彼此聯(lián)系,強調(diào)信道在消息傳遞中的作用,不謀求形成流水線。
消息的發(fā)送者和接受者通過該信道松耦合,發(fā)送者不知道自己消息被哪個接受者消費了,接受者也不知道是從哪個發(fā)送者發(fā)送的消息。
go的信道
go的信道[1]是golang協(xié)程同步和通信的原生方式。
同map,slice一樣,channel通過make內(nèi)置函數(shù)初始化并返回引用,引用可認為是常量指針[2]。
兩種信道:
1. 無緩沖區(qū)信道:讀寫兩端就緒后,才能通信(一方?jīng)]就緒就阻塞)。
這種方式可以用來在goroutine中進行同步,而不必顯式鎖或者條件變量。
2. 有緩沖區(qū)信道:就有可能不阻塞, 只有buffer滿了,寫入才會阻塞;只有buffer空了,讀才會阻塞。
go的信道暫時先聊到這里。
我們來用以上背景做一道 有意思的面試題吧 。
兩個線程輪流打印0到100?
我不會啥算法,思路比較弱智:#兩線程#, #打印奇/偶數(shù)#, 我先復(fù)刻這兩個標簽。
通過go的無緩沖信道的同步阻塞的能力對齊每一次循環(huán)。
package main
import (
"fmt"
"strconv"
"sync"
)
var wg sync.WaitGroup
var ch1 = make(chan struct{})
func main() {
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i <= 100; i++ {
ch1 <- struct{}{}
if i%2 == 0 { // 偶數(shù)
fmt.Println("g0 " + strconv.Itoa(i))
}
}
}()
go func() {
defer wg.Done()
for i := 0; i <= 100; i++ {
<-ch1
if i%2 == 1 { // 奇數(shù)
fmt.Println("g1 " + strconv.Itoa(i))
}
}
}()
wg.Wait()
}
題解:兩個協(xié)程都執(zhí)行0到100次循環(huán),但是不管哪個線程跑的快,在每次循環(huán)輸出時均會同步對齊, 每次循環(huán)時只輸出一個奇/偶值, 這樣也不用考慮兩個協(xié)程的啟動順序。
思考我的老牌勁語C#要完成本題要怎么做?
依舊是#兩線程#、#打印奇偶數(shù)#, 我沒找到C#中能多次對齊線程的能力, 于是使用兩線程相互通知的方式。
volatile static int i = 0;
static AutoResetEvent are = new AutoResetEvent(true);
static AutoResetEvent are2 = new AutoResetEvent(false);
public static void Main(String[] args)
{
Thread thread1 = new Thread(() =>
{
for (var i=0;i<=100;i++)
{
are.WaitOne();
if (i % 2 == 0)
{
Console.WriteLine(i + "== 偶數(shù)");
}
are2.Set();
}
});
Thread thread2 = new Thread(() =>
{
for (var i = 0; i <= 100; i++)
{
are2.WaitOne();
if (i % 2 == 1)
{
Console.WriteLine(i + "== 奇數(shù)");
}
are.Set();
}
});
thread1.Start();
thread2.Start();
Console.ReadKey();
}
注意:
- volatile:提醒編譯器或運行時系統(tǒng)不對字段做優(yōu)化(處于性能性能,編譯器/runtime會對同時執(zhí)行的線程訪問的同一字段進行優(yōu)化,加volatile忽略這種優(yōu)化 )。
- Object-->MarshalByRefObject-->WaitHandle-->EventWaitHandle--->AutoResetEvent[3] 本次使用了2個自動重置事件來切換通知,由一個線程通知另外一個線程執(zhí)行。
引用鏈接
[1] go的信道: https://www.runoob.com/w3cnote/go-channel-intro.html
[2] 常量指針: https://zhuanlan.zhihu.com/p/133225100
[3] AutoResetEvent: https://docs.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-6.0