進階並行與非同步

演員模型與信箱(actor model and the mailbox)

想像一間公司,員工從不去翻彼此的抽屜。要把事情辦成,你寄一張便條到同事的收件匣;他們一次處理一張便條,在自己的私有檔案上做事,再把便條寄回。沒有人共用桌子,於是不會有人為了搶釘書機而起爭執。演員模型就是完全照這方式建構的並行:基本單位是演員,一個小小的隔離實體,擁有自己的私有狀態,且只靠收發訊息來溝通——絕不去碰另一個演員的記憶體。

每個演員有一個信箱:一個讓進來的訊息落腳的佇列。演員嚴格地一次一個、依序地處理信箱裡的訊息。這條「單一消費者」規則就是全部的訣竅——因為一個演員一次只處理一則訊息,它的私有狀態從不被並行碰觸,於是它內部沒有資料競爭、完全不需要任何鎖。要影響另一個演員,你寄一則訊息給它(非同步地:寄出就往下走);收件者終究會從信箱裡把它取出來並反應——更新自己的狀態、寄出更多訊息、或生出新演員。這是訊息傳遞而非共享記憶體:沒有需要保護的共享可變狀態,只有在隔離的演員之間流動的訊息。演員常享有位置透明性——不論一個演員住在這個行程裡、還是在網路另一頭的機器上,你都用同樣的方式寄到它的位址,這正是 Erlang/Akka 系統得以跨節點擴展之道。

演員之所以重要,是因為它藉著移除難題(安全並行)的成因來讓它變得可解:沒有共享可變狀態,就沒有鎖、沒有資料競爭、也沒有因鎖順序而生的死結。它撐起了 Erlang/Elixir(電信級可靠性)、Akka 與 Orleans。「不要靠共享記憶體來溝通;要靠溝通來共享記憶體」這句話(出自 Go 的通道,一位近親)抓住了它的精神。誠實的提醒:演員用新的臭蟲換掉鎖的臭蟲——無界的信箱在過載下會無止境地增長(需要背壓)、不同寄件者之間的訊息順序沒有保證、而一來一回的請求/回覆比一次函式呼叫彆扭。它移除了共享記憶體的危險;它並沒有移除「思考並行」這件事。

一個計數器演員:它唯一的狀態是 'count'。訊息:Inc、Get(reply_to)。它一次處理一則,所以 'count += 1' 從不競爭。要讀它,你寄 Get 並等待一則回覆訊息——沒有鎖、沒有共享變數。

一個信箱、一個消費者:序列地處理訊息,使演員的私有狀態無須任何鎖就免於競爭。

演員移除了資料競爭,但非所有並行問題:信箱在負載下可能滿溢(要施加背壓)、跨不同寄件者的訊息順序沒有保證、而請求/回覆比一次呼叫笨拙。「無共享記憶體」是它的好處;它不是免費的午餐。

又稱
actorsmessage-passing concurrency演員模型訊息傳遞並行