iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0

Actor model是一個物件導向與函數導向混合的例子,在併發的情況下需要避免相同狀態的共享,也就是說作業要避免讀寫同一個資料來源。如果能夠將作業拆分,就可以大幅度的減少共享的部分,這邊要注意的是,Actor model並不能直接解決併發的問題,而是利用這個模型能夠容易的將計算拆分,計算單元內不一定不共享資料,但不同的計算單元間運算是獨立的。Actor model中我們將一個計算單元視為一個Actor,而一個Actor具有幾個功能

  1. 執行作業
  2. 創建其他的Actor
  3. 傳送訊息給其他的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元的要求,可能發生的情況有

  • 第一個與第二個要求取得的餘額都是100,連續提領兩次取得120元,而存款帳戶餘額卻還是40,也就是說被多領了60元系統卻不知道
  • 第一次取得60元,第二次要取得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


上一篇
Day28. Concurrency
下一篇
Day30. 結語
系列文
Functional Programming with C#30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言