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:顧客、服務生和派的展示櫃。

訊息流程#

  1. 外部告訴顧客「你餓了」
  2. 顧客向服務生發送點餐訊息
  3. 服務生向展示櫃請求取得一片派
  4. 如果有派,展示櫃直接送給顧客,同時通知服務生加入帳單
  5. 如果沒有派,展示櫃告訴服務生,服務生向顧客道歉

關鍵觀察#

注意在這個設計中完全沒有任何並行處理的程式碼。沒有鎖、沒有信號量、沒有同步機制。但它可以完美地處理並行——因為 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,黑板