首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

ActorLite:輕量級 Actor 模型實現(下)—— 以 F# 釋放模式匹配的威力

     編輯:關於.NET

在上一篇文章中,我們構建了一個基於 C# 的簡易 Actor 模型。雖然繼承 Actor<T> 並實作 Receive 方法即可建立 Actor,但 C# 的強型別系統在表達「訊息結構」時顯得笨拙。Erlang 以動態元組與原子輕鬆描述訊息語意,C# 卻需在 object[]、object 型別檢查、或枚舉加參數集合之間兩難。本文將示範 F# 如何以判別聯集、元組與強大的模式匹配,優雅地化解這一痛點,並透過一個聊天室與網頁爬蟲範例,展示 Actor 模型在 F# 下的表達力與並發優勢。

C# 面臨的訊息建模困境

Erlang 的訊息完全動態,開發者可隨意發送、接收任意型別,靈活度極高。C# 雖擁有更強的型別系統,但缺乏「動態性」,導致訊息定義僵化。以簡單聊天程式為例,Erlang 僅需模式匹配不同元組即可表達 start、greeting、say、bye 等指令。C# 若要達到同樣效果,通常有三種妥協做法:

  1. 使用 object[] 並手動檢查元素,極其繁瑣;
  2. 以 object 為訊息型別,再透過型別判斷分派,需「先定義後使用」,擴充性差;
  3. 採用枚舉或字串代表命令,搭配參數集合,若有「分發類庫」支援尚可接受,否則同樣缺乏彈性。

這正是老趙正在努力攻克的難題,也促使我們轉向 F# 尋找突破口。

F# 的三大利器:靈活型別、模式匹配、自由語法

F# 雖為強型別編譯語言,但其型別系統遠比 C# 靈活。針對聊天案例,我們可定義如下訊息型別:

type Message = string
type ChatMsg =
    | Start of Person
    | Greeting of Person * Message
    | Say of Person * Message
    | Bye of Person * Message

此處運用了三大特性:

  • 型別別名:type Message = string 賦予語意,且為全域有效,優於 C# 的 using 別名。
  • 判別聯集:ChatMsg 以多個判別子(Start、Greeting…)封裝不同資料結構,每個判別子可攜帶不同型別參數。
  • 元組:Person * Message 以順序組合現有型別,輕鬆構建複合資料。

以模式匹配實作 Actor 邏輯

定義完訊息型別,Person Actor 的 Receive 方法即可透過 match 對不同判別子分派處理:

and Person(name: string) =
    inherit ChatMsg Actor()

    let GetRandom =
        let r = new Random(DateTime.Now.Millisecond)
        fun () -> r.NextDouble()

    member self.Name = name

    override self.Receive(message) =
        match message with
        // 系統要求發起聊天
        | Start(p) ->
            Console.WriteLine("系統讓{0}向{1}打招呼", self.Name, p.Name)
            Greeting(self, "Hi, 有空不?") |> p.Post

        // 打招呼
        | Greeting(p, msg) ->
            Console.WriteLine("{0}向{1}打招呼:{2}", p.Name, self.Name, msg)
            if GetRandom() < 0.8 then
                Say(self, "好,聊聊。") |> p.Post
            else
                Bye(self, "沒空,bye!") |> p.Post

        // 進行聊天
        | Say(p, msg) ->
            Console.WriteLine("{0}向{1}說道:{2}", p.Name, self.Name, msg)
            if GetRandom() < 0.8 then
                Say(self, "繼續聊。") |> p.Post
            else
                Bye(self, "聊不動了,bye!") |> p.Post

        // 結束
        | Bye(p, msg) ->
            Console.WriteLine("{0}向{1}再見:{2}", p.Name, self.Name, msg)

注意管線運算子 |> 的運用:x |> f 相當於 f(x),將巢狀呼叫轉為線性「訊息發送」風格,極貼合 Actor 模型的直覺。

啟動聊天室

let startChat() =
    let p1 = new Person("Tom")
    let p2 = new Person("Jerry")
    let p3 = new Person("老趙")
    Start(p2) |> p1.Post
    Start(p3) |> p2.Post

startChat()

執行結果將根據隨機機率呈現不同對話流程,完美展示非同步、無共享狀態的並發互動。

實戰範例:多 Actor 協作的網頁爬蟲

更貼近實務的例子是多 Actor 協作抓取網頁。Crawler Actor 僅負責下載並回傳結果;monitor 以單例模式接收 URL、分派任務、收集結果:

type Crawler() =
    inherit ((obj Actor) * string) Actor()

    override self.Receive(message) =
        let (monitor, url) = message
        let content = (new WebClient()).DownloadString(url)
        (url, content) |> monitor.Post

let monitor =
    { new obj Actor() with
        override self.Receive(message) =
            match message with
            | :? string as url -> (self, url) |> (new Crawler()).Post
            | :? (string * string) as p ->
                let (url, content) = p
                Console.WriteLine("{0} => {1}", url, content.Length)
            | _ -> failwith "Unrecognized message" }

let urls = [
    "https://www.live.com"
    "https://www.baidu.com"
    "https://www.google.com"
    "https://www.cnblogs.com"
    "https://www.microsoft.com" ]

List.iter monitor.Post urls

執行結果顯示各網站 HTML 長度,證明多 Actor 並行抓取運作正常。

效能分析與優化方向

此 Actor 模型唯一的鎖在於訊息佇列存取,若換成無鎖佇列即可達成完全無鎖實現。然而調度頻率過高(每訊息一次調度)可能成為瓶頸。優化策略包括:

  • 批次處理:Dispatcher.Execute 每次取出 n 則訊息或超過時間閾值再調度;
  • 替換線程池:若 .NET 內建線程池受其他負載影響,可改用獨立線程池;
  • 建立分析機制:所有優化必須以 Profile 數據為依據,避免盲目調整。

來源:https://www.cnblogs.com/JeffreyZhao/archive/2009/05/16/a-simple-actor-model-implementation-3.html

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved