程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> ActorLite:一個輕量級Actor模型實現(下)

ActorLite:一個輕量級Actor模型實現(下)

編輯:關於.NET

在上一篇文章中,我們實現了一個簡單的Actor模型。如果要構建一個Actor,便只是 簡單地繼承Actor<T>類型並實現其Receive方法即可。在上次文章的末尾,我們使 用C#演示了該Actor模型的使用。不過現在我們將嘗試一下F#。

C#使用Actor模型的缺陷

在Erlang中,每個消息都使用模式匹配來限制其“結構”或“格式”,以此表達不同 含義。C#類型系統的抽象能力遠勝於Erlang,但是Erlang的“動態性”使得開發人員可以 在程序中隨意發送和接收任何類型,這種“自由”為Erlang帶來了靈活。我們的Actor模 型中,每個Actor對象都需要一種特定的消息格式,而這種消息格式承擔了“表現Actor所 有職責”的重任,但是一個Actor的職責是可能由任何數據組合而成。例如一段最簡單的 “聊天”程序,其Actor表示了一個“人”,用Erlang實現可能就會這麼寫:

loop() ->
   receive
     % 系統要求發起聊天,於是向對方打招呼
     {start, Person} ->
       Person ! {self(), {greeting, "你好")},
       loop();

     % 有人前來發起聊天,於是向對方說了點什麼
     {Person, {greeting, Message}} ->
       Person ! {self(), {say, "..."}},
       loop();

     % 有人前來說話,於是拜拜
     {Person, {say, Message}} ->
       Person ! {self(), {bye, "..."}},
       loop();

     ...
   end.

不同的元組(tuple)配合不同的原子(atom)便表示了一條消息的“含義”,但是使 用C#您又該怎樣來表現這些“命令”呢?您可能會使用:

使用object[]作為消息類型,並檢查其元素。

使用object作為消息類型,並判斷消息的具體類型。

使用枚舉或字符串代表“命令”,配合一個參數集合。

第1種做法十分麻煩;第2種則需要“先定義,後使用”也頗為不易;而第3種做法,平 心而論,如果有一個“分發類庫”的支持就會比較理想——可能比這篇文章中的F#還要理 想。老趙正在努力實現這一功能,因為C#的這個特性會影響到.NET平台下所有Actor模型 (如第一篇文章中所提到的CCR或Retlang)的使用。

而目前,我們先來看看F#是否可以略為緩解一下這方面的問題。

在F#中使用Actor模型

Erlang沒有嚴謹的類型系統,其“消息類型”是完全動態的,因此非常靈活。那麼F# 又有什麼“法寶”可以解決C#中所遇到的尴尬呢?在現在這個問題上,F#有三個領先於C# 的關鍵:

靈活的類型系統

強大的模式匹配

自由的語法

雖然F#也是強類型的編譯型語言(這點和C#一致),但是F#的類型系統較C#靈活許多 ,例如在“聊天”這個示例中,我們就可以編寫如下類型作為“消息”類型:

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

在這個定義中用到了F#類型系統中的三個特點:

類型別名:即type Message = string。為一個已有的類型定義一個別名,可以得到更 好的語義。與C#使用using定義別名不同的是,F#中的別名可以定義為全局性的,而不僅 僅是“源代碼”級別的別名。

Discriminated Unions:即type ChatMsg = …。Discriminated Unions可以為一個類 型指定多個discriminator,每個discriminator由一個名稱,以及另一種具體類型來表示 。不同的discriminator的具體類型可以不同。

元組(Tuple):即Person * Message。在F#中可以通過把現有類型按順序進行任意組 合來得到新的類型,這種類型便被稱為“元組”。

在Actor模型中,我們便組合了F#的三個特別特性,定義了消息的具體類型。而在使用 時,我們便可以使用“模式匹配”對不同的“消息”——其實是CharMsg的不同 discriminator進行不同地處理。於是具體的Actor類型Person,便可以使用如下定義:

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

Person類的構造函數接受一個name作為參數,並將其放置到Name屬性中。我們同時定 義了GetRandom函數,它會在內部構造一個System.Random對象,並每次返回NextDouble方 法的值(請注意,無論調用多少次GetRandom方法,永遠使用了同一個Random對象,因為 他是在定義GetRandom方法時創建的)。而在override的Receive方法中,我們使用“模式 匹配”對message對象進行處理:

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

請注意上述最後一行,原本我們使用p.Post(…)的調用方式,現在使用了“|>”符 號代替。在F#中,x |> f便代表了f(x),它的本意是可以把f(g(h(x)))這樣冗余的調 用方式轉變為清晰的“消息發送”形式:x |> h |> g |> f。而“消息發送” 也恰好是我們所需要的“感覺”。因此,我們在接下來的代碼中也使用這樣的方式:

// 打招呼
     | 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)

至此,Person類型定義完畢。我們構造三個Person對象,讓它們隨意聊天:

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()

結果如下(內容會根據隨機結果不同而有所改變):

系統讓Tom向Jerry打招呼
系統讓Jerry向老趙打招呼
Jerry向老趙打招呼:Hi, 有空不?
Tom向Jerry打招呼:Hi, 有空不?
Jerry向Tom說道:好,聊聊。
老趙向Jerry說道:好,聊聊。
Jerry向老趙說道:繼續聊。
Tom向Jerry說道:繼續聊。
Jerry向Tom說道:繼續聊。
老趙向Jerry說道:繼續聊。
Jerry向老趙說道:繼續聊。
Tom向Jerry再見:聊不動了,bye!
老趙向Jerry說道:繼續聊。
Jerry向老趙再見:聊不動了,bye!

使用Actor模型抓取網絡數據

我們再來看一個略為“現實”一點的例子,需要多個Actor進行配合。首先,我們定義 一個“抓取”數據用的Actor,它的唯一作用便是接受一個消息,並將抓取結果傳回:

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

再使用“單件”方式直接定義一個monitor對象:

let monitor =
   { new obj Actor() with
     override self.Receive(message) =
       match message with
       // crawling
       | :? string as url -> (self, url) |> (new  Crawler()).Post

       // get crawled result
       | :? (string * string) as p ->
         let (url, content) = p
         Console.WriteLine("{0} => {1}", url,  content.Length)

       // unrecognized message
       | _ -> failwith "Unrecognized message" }

每次收到“抓取”消息時,monitor都會創建一個Crawler對象,並把url發送給它,並 等待回復消息。而在使用時,只要把對象一個一個“發送”給monitor便可:

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

List.iter monitor.Post urls

運行結果如下:

http://www.live.com => 18035
http://www.google.com => 6942
http://www.cnblogs.com => 62688
http://www.microsoft.com => 1020
http://www.baidu.com => 3402

性能分析

最後,我們再對這個Actor模型的性能作一點簡單的分析。

如果從“鎖”的角度來說,這個Actor模型唯一的鎖是在消息隊列的訪問上,這基本上 就是唯一的瓶頸。如果把它替換為lock-free的隊列,那麼整個Actor模型就是完全的 lock-free實現,其“調度”性能可謂良好。

不過,從另一個角度來說,這個Actor模型的調度非常頻繁,每次只執行一個消息。試 想,如果執行一個消息只需要50毫秒,而進行一次調度就需要100毫秒,那麼這個性能的 瓶頸還是落在“調度”上。因此,如果我們需要進一步提高Actor模型的性能,則需要從 Dispatcher.Execute方法上做文章,例如把每次執行一個消息修改為每次執行n個消息, 或超過一個時間的阈值再進行下一次調度。減少調度,也是提高Actor模型性能的關鍵之 一。

此外,如果覺得.NET自帶的線程池性能不高,或者說會受到程序其他部分的影響,那 麼也可以使用獨立的線程池進行替換。

自然,任何性能優化都不能只憑感覺下手,一切都要用數據說話,因此在優化時一定 要先建立合適的Profile機制,保證每一步優化都是有效的。

來源:

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved