並行模型Actor

並行開發時經常需要關注加鎖和原子操作等一系列線程問題,而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負責寫入文件,相比同一段邏輯完成該操作大概提升一倍性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章