Actor model是一個物件導向與函數導向混合的例子,在併發的情況下需要避免相同狀態的共享,也就是說作業要避免讀寫同一個資料來源。如果能夠將作業拆分,就可以大幅度的減少共享的部分,這邊要注意的是,Actor model並不能直接解決併發的問題,而是利用這個模型能夠容易的將計算拆分,計算單元內不一定不共享資料,但不同的計算單元間運算是獨立的。Actor model中我們將一個計算單元視為一個Actor,而一個Actor具有幾個功能
上面的解釋應該難以理解,下面是一個Actor可能的實作
public class Actor<Msg>
{
// 每個Actor中會儲存待執行的作業
privte Queue<Msg> msgQ { get; set; }
// 可以通知該Actor待執行的作業
public void Tell(Msg msg) => msgQ.Enqueue(msg)
// Actor執行作業
public void Start(Action<Msg> action)
{
while(true)
{
action(msgQ.Dequeue())
}
}
}
實際在使用的時後應該要實作生產消費模式,讓queue為空的時候作業暫停,有心得資料時要繼續作業,另外需要能夠管理目前使用中的Actor,扮演管理者角色的可以是主程序或是另一個Actor。
實際使用上我們可以舉一個例子,假設有一個帳戶系統,當下的餘額為100,而使用者連續送出了兩次提領60元的要求,可能發生的情況有
很顯然我們要的是第二個
public actorManager
{
private ConcurrentDictionary<Actor> actorDic { get; set; }
public void Withdraw(request r)
{
actorDic.GetOrAdd(r.Account, new Actor().Start(WithdrawRequest)).Tell(r);
}
}
void WithdrawRequest(r)
{
var account = GetAccount(r.Name);
account.Balance -= r.amount;
if(account.Balance < 0) throw new InvalidOperationException("餘額不足");
Update(account);
}
actorDic負責管理不同的演員實體,透過Tell方法來實現消息傳遞。要注意的是這裡使用帳戶名稱來分類作業,在當前的例子中僅有一個帳戶的情況下可能看不出差別,但是如果有多個帳戶的情況下,因為不同帳戶的存取都是獨立的,就可以分別進行,也就`是一開始說的透過actor model讓工作容易拆分,而每個演員內部則是共用欄位,並且不在演員內部進行併發作業。
actor model中的actor很類似物件導向中的object,實作上以OO的概念也會比較容易,不過使用上的概念比較接近FP,透過傳遞消息而不共享記憶體,並且要注意作業之間的純粹性,也就是說使用純函式較容易達到目的。有一些套件實作了演員模型,並且提供函數式的API使用,可以參考akka.net。