Without writers, stories would not be written, > Without actors, stories could not be brought to life.
— Angie-Marie Delsante
核心概念#
Actor 和 Process 提供了有趣的並行實作方式,不需要承擔同步存取共享記憶體的負擔。
首先定義:
- Actor:一個獨立的虛擬處理器,擁有自己的本地(且私有的)狀態。每個 actor 有一個信箱。當信箱中出現訊息且 actor 閒置時,它會醒來處理該訊息。處理完後,它處理下一則訊息,或者如果信箱為空就回去休眠。在處理訊息時,actor 可以創建其他 actor、發送訊息給它知道的其他 actor、以及創建新狀態供處理下一則訊息時使用。
- Process:通常是更通用的虛擬處理器,由作業系統實作以促進並行。Process 可以被約束(依慣例)表現得像 actor,本節所說的 process 即指這種型態。
Actor 的關鍵特性#
有一些在 actor 的定義中找不到的東西:
| 特性 | 說明 |
|---|---|
| 沒有單一控制者 | 沒有什麼東西在排程接下來發生什麼,或協調從原始資料到最終輸出的資訊傳遞 |
| 唯一的狀態存在於訊息和本地狀態中 | 訊息只能被接收者讀取,本地狀態在 actor 外部無法存取 |
| 所有訊息都是單向的 | 沒有回覆的概念。如果你想要 actor 回應,你在訊息中附上自己的信箱地址,它(最終)會把回應作為另一則訊息送到那個信箱 |
| 一次處理一則訊息 | 處理到完成 |
因此,actor 是並行執行的、非同步的、而且不共享任何東西。
Tip 59 - Use Actors For Concurrency Without Shared State(使用 Actor 實現無共享狀態的並行)
一個簡單的 Actor 範例#
書中以餐廳場景用 JavaScript(Nact 函式庫)實作了三個 actor:顧客、服務生和派的展示櫃。
訊息流程#
- 外部告訴顧客「你餓了」
- 顧客向服務生發送點餐訊息
- 服務生向展示櫃請求取得一片派
- 如果有派,展示櫃直接送給顧客,同時通知服務生加入帳單
- 如果沒有派,展示櫃告訴服務生,服務生向顧客道歉
關鍵觀察#
注意在這個設計中完全沒有任何並行處理的程式碼。沒有鎖、沒有信號量、沒有同步機制。但它可以完美地處理並行——因為 actor 天生一次只處理一則訊息,展示櫃永遠不會出現兩個服務生同時拿派的競爭狀態。
當五個請求同時送出(顧客 1 要三片、顧客 2 要兩片),三片派被正確分配,多出的請求會收到「沒有派了」的道歉訊息。而且每次執行的順序可能不同,因為訊息是非同步的。
沒有顯式的並行#
在 actor 模型中,不需要寫任何處理並行的程式碼,因為沒有共享狀態。也不需要編寫明確的端到端「先做這個、再做那個」的邏輯,因為 actor 會根據收到的訊息自行處理。
也沒有提到底層架構。這組元件在單一處理器、多核心、或多台網路機器上都能同樣運作。
Erlang 開創先河#
Erlang 語言和執行環境是 actor 實作的絕佳範例(儘管 Erlang 的發明者當初並沒有讀過原始的 Actor 論文)。Erlang 稱 actor 為 processes,但它們不是一般的作業系統程序。就像我們討論的 actor 一樣,Erlang process 是輕量級的(可以在一台機器上跑數百萬個)、透過訊息傳遞溝通、彼此隔離且不共享狀態。
此外 Erlang 執行環境實作了監督系統(supervision system),管理 process 的生命週期,可以在故障時重新啟動 process 或 process 群組。Erlang 還支援熱更新程式碼——可以在不停機的情況下替換執行中系統的程式碼。Erlang 系統運行著世界上最可靠的一些程式碼,常常號稱達到「九個九」的可用性。
建議: Erlang(及其後代 Elixir)不是唯一的選擇——大多數語言都有 actor 的實作框架。考慮在你的並行實作中使用它們。
相關章節#
- Topic 28,去耦合
- Topic 30,轉換式程式設計
- Topic 36,黑板