源於從Erlang到Go的一些思惟碰撞,就像當初從C++到Erlang同樣,整理下來記於此。git
Actor
Actor模型,又叫參與者模型,其」一切皆參與者(actor)」的理念與面向對象編程的「一切皆是對象」相似,可是面向對象編程中對象的交互一般是順序執行的(佔用的是調用方的時間片,是否併發由調用方決定),而Actor模型中actor的交互是並行執行的(不佔用調用方的時間片,是否併發由本身決定)。github
在Actor模型中,actor執行體是第一類對象,每一個actor都有本身的ID(類比人的身份證),能夠被傳遞。actor的交互經過發送消息來完成,每一個actor都有一個通訊信箱(mailbox,本質上是FIFO消息隊列),用於保存已經收到但還沒有被處理的消息。actorA要向actorB發消息,只需持有actorB ID,發送的消息將被當即Push到actorB的消息信箱尾部,而後返回。所以Actor的通訊原語是異步的。編程
從actor自身來講,它的行爲模式可簡化爲:併發
- 發送消息給其它的actor
- 接收並處理消息,更新本身的狀態
- 建立其它的actor
一個好的Actor模型實現的設計目標:框架
- 調度器: 實現actor的公平調度
- 容錯性: 具有良好的容錯性和完善錯誤處理機制
- 擴展性: 屏蔽actor通訊細節,統一本地actor和遠程actor的通訊方式,進而提供分佈式支持
- 熱更新? (還沒弄清楚熱更新和Actor模型,函數式範式的關聯性)
在Actor模型上,Erlang已經耕耘三十餘載,以上提到的各個方面都有很是出色的表現,其OTP整合了在Actor模型上的最佳實踐,是Actor模型的標杆。異步
CSP
順序通訊進程(Communicating sequential processes,CSP)和Actor模型同樣,都由獨立的,併發的執行實體(process)構成,執行實體間經過消息進行通訊。但CSP模型並不關注實體自己,而關注發送消息使用的通道(channel),在CSP中,channel是第一類對象,process只管向channel寫入或讀取消息,並不知道也不關心channel的另外一端是誰在處理。channel和process是解耦的,能夠單首創建和讀寫,一個process能夠讀寫(訂閱)個channel,一樣一個channel也可被多個process讀寫(訂閱)。分佈式
對每一個process來講:函數
- 從命名channel取出並處理消息
- 向命名channel寫入消息
- 建立新的process
Go語言並無徹底實現CSP理論(參見知乎討論),只提取了CSP的process和channel的概念爲併發提供理論支持。目前Go已是CSP的表明性語言。post
CSP vs Actor
- 相同的宗旨:」不要經過共享內存來通訊,而應該經過通訊來共享內存」
- 二者都有獨立的,併發執行的通訊實體
- Actor第一類對象爲執行實體(actor),CSP第一類對象爲通訊介質(channel)
- Actor中實體和通訊介質是緊耦合的,一個Actor持有一個Mailbox,而CSP中process和channel是解耦的,沒有從屬關係。從這一層來講,CSP更加靈活
- Actor模型中actor是主體,mailbox是匿名的,CSP模型中channel是主體,process是匿名的。從這一層來講,因爲Actor不關心通訊介質,底層通訊對應用層是透明的。所以在分佈式和容錯方面更有優點
Go vs Erlang
- 以上 CSP vs Actor
- 均實現了語言級的coroutine,在阻塞時能自動讓出調度資源,在可執行時從新接受調度
- go的channel是有容量限制的,所以只能必定程度地異步(本質上仍然是同步的),erlang的mailbox是無限制的(也帶來了消息隊列膨脹的風險),而且erlang並不保證消息是否能到達和被正確處理(但保證消息順序),是純粹的異步語義,actor之間作到徹底解耦,奠基其在分佈式和容錯方面的基礎
- erlang/otp在actor上擴展了分佈式(支持異質節點),熱更和高容錯,go在這些方面還有一段路要走(受限於channel,想要在語言級別支持分佈式是比較困難的)
- go在消息流控上要作得更好,由於channel的兩個特性: 有容量限制並獨立於goroutine存在。前者能夠控制消息流量並反饋消息處理進度,後者讓goroutine自己有更高的處理靈活性。典型的應用場景是扇入扇出,Boss-Worker等。相比go,erlang進程老是被動低處理消息,若是要作流控,須要本身作消息進度反饋和隊列控制,靈活性要差不少。另一個例子就是erlang的receive操做須要遍歷消息隊列(參考),而若是用go作同步調用,經過單獨的channel來作則更優雅高效
Actor in Go
在用Go寫GS框架時,不自覺地會將goroutine封裝爲actor來使用:設計
- GS的執行實體(如玩家,公會)的邏輯具有強狀態和功能聚合性,不易拆分,所以一般是一個實體一個goroutine
- 實體接收的邏輯消息具有弱優先級,高順序性的特色,所以一般實體只會暴露一個Channel與其它實體交互(結合go的interface{}很容易統一channel類型),這個channel稱爲RPC channel,它就像這個goroutine的ID,幾乎全部邏輯goroutine之間經過它進行交互
- 除此以外,實體還有一些特殊的channel,如定時器,外部命令等。實體goroutine對這些channel執行select操做,讀出消息進行處理
- 加上goroutine的狀態數據以後,此時的goroutine的行爲與actor類似:接收消息(多個消息源),處理消息,更新狀態數據,向其它goroutine發送消息(經過RPC channel)
到目前爲止,goroutine和channel解耦的優點並未體現出來,我認爲主要的緣由仍然是GS執行實體的強狀態性和對異步交互流程的順序性致使的。
在研究這個問題的過程當中,發現已經有人已經用go實現了Actor模型: https://github.com/AsynkronIT/protoactor-go。 支持分佈式,甚至supervisor,總體思想和用法和erlang很是像,真是有種他山逢知音的感受。:)
參考:
- http://jolestar.com/parallel-programming-model-thread-goroutine-actor/
- https://www.zhihu.com/question/26192499
http://wudaijun.com/2017/05/go-vs-erlang/