並行開發時經常需要關注加鎖和原子操作等一系列線程問題,而Actor模型內部狀態由它自己維護,內部數據只能自己修改,因此Actor不需要過多關注線程問題。
Actor模型
Actor由狀態(State)、郵箱(Mailbox)和行爲(Behavior)組成。
NewLife.Model.Actor 設計爲一個輕量級Actor模型,麻雀雖小五臟俱全!
/// <summary>無鎖並行編程模型</summary>
/// <remarks>
/// 獨立線程輪詢消息隊列,簡單設計避免影響默認線程池。
/// 適用於任務顆粒較大的場合,例如IO操作。
/// </remarks>
public interface IActor
{
/// <summary>添加消息,驅動內部處理</summary>
/// <param name="message">消息</param>
/// <param name="sender">發送者</param>
/// <returns>返回待處理消息數</returns>
Int32 Tell(Object message, IActor sender = null);
}
IActor 接口設計爲只有一個Tell方法,用於傳遞要發通知給目標Actor。這裏的message可以列極爲狀態(State)。目標Actor內部有個隊列收集信息,該隊列可以認爲是郵箱(Mailbox)。Actor內部有獨立線程去讀取 郵箱數據進行處理,即是行爲(Behavior)!
從某種程度上來講,Actor就是一個隊列加上獨立線程。如果該隊列設計爲分佈式隊列,那麼這個Actor就成了真正的分佈式並行模型。(如果Actor配上RedisQueue又會怎麼樣呢?)
Actor可以一個接着一個,串聯起來,形成分佈式併發處理集羣。儘管是分佈式系統,然而所有代碼完全不需要考慮多線程問題。
總之,Actor模型的理念非常簡單:萬物皆Actor!
應用例程
我們來看一段測試代碼,默認讀取數據庫然後生成Excel文件,兩步都是比較耗時的操作。
[Fact]
public async void Test1()
{
var sw = Stopwatch.StartNew();
var actor = new BuildExcelActor();
for (var i = 0; i < 6; i++)
{
// 模擬查詢數據,耗時500ms
XTrace.WriteLine("讀取數據……");
await Task.Delay(500);
// 通知另一個處理器
actor.Tell($"數據行_{i + 1}");
}
// 等待最終完成
actor.Stop();
sw.Stop();
Assert.True(sw.ElapsedMilliseconds > 6 * 500);
Assert.True(sw.ElapsedMilliseconds < 6 * 500 + 500);
}
private class BuildExcelActor : Actor
{
protected override async Task ReceiveAsync(ActorContext context)
{
XTrace.WriteLine("生成Excel數據:{0}", context.Message);
await Task.Delay(500);
}
}
新建一個實現類繼承Actor,重寫ReceiveAsync,實現Actor行爲(業務邏輯)。此時獨佔線程,所以無需考慮多線程問題。
跑起來看看日誌輸出
從上圖可以看出,主邏輯和Actor是並行執行。主邏輯把數據送給Actor後,繼續去讀取下一頁,無需等待Actor生成完成。
總結
XCode的實體延遲隊列,就是一個貨真價實的Actor模型。主邏輯只管處理業務,數據保存操作由另一個Actor完成。
此處爲語雀文檔,點擊鏈接查看:https://www.yuque.com/go/doc/7553851
DAL的數據導出、恢復和備份,也是Actor模型,主邏輯操作數據庫,獨立Actor負責寫入文件,相比同一段邏輯完成該操作大概提升一倍性能。