java actor模型实例,详解Theron通过Actor模型解决C++并发编程的一种思维
現今,單臺機器擁有多個獨立的計算單元已經太常見了,這點在服務器的處理器上表現尤為明顯,據AMD的一張2012-2013服務器路線圖顯示,服務器處理器的核心數將在2013年達到20顆之多,合理的利用CPU資源已是一個不得不考慮的問題。
不少C++程序員依然使用著多線程模型,但是對多線程的掌控并不是一件容易的事情,開發中容易出錯、難以調試,有些開發者為了避免多線程帶來的復雜度而棄用多線程,有些開發者則另投其他語言陣營,例如:Erlang,其實我們還有其他的選擇,Theron就是其中之一。
1、什么是Theron?
Theron是一個用于并發編程的C++庫,通過Theron我們可以避免多線程開發中各種痛處,例如:共享內存、線程同步,Theron通過Actor模型向我們展示了另一種思維。
2、什么是Actor模型?
Erlang因為其優秀的并發特性而被大家所關注,而其并發特性的關鍵之一就是在于其采用了Actor模型,與Actor模型相對應的模型則是我們在面向對象編程中使用的Object模型,Object模型中宣揚,一切皆為Object(對象),而Actor模型則認為一切皆為Actor。
Actor模型中,Actor之間通過消息相互通信,這是其和Object模型的一個顯著的區別,換而言之Actor模型使用消息傳遞機制來代替了Object模型中的成員方法調用。
在馬海祥看來,這樣做意義重大,因為相對于成員方法的調用來說,消息的發送是非阻塞的,它無需等待被調用方法執行完成就可以返回,下圖顯示了此種區別:
A::a()調用了objB.b(),此時A::a()必須等待B::b()的返回才能繼續執行,在Actor模型中,對應的做法是Actor A向Actor B發送消息并立即返回,這時候Actor A可以繼續執行下去,與此同時Actor B收到消息被喚醒并和Actor A并行執行下去。
Theron中的每個Actor都會綁定一個唯一的地址,通過Actor的地址就可以向其發送消息了,每個Actor都有一個消息隊列。
從編碼者的角度看來,每實例化一個Actor都創建了一個和Actor相關的“線程”(非系統級的線程),每個Actor總是被單線程的執行。
總的來說,Theron的并發特性的關鍵就在于:每個Actor在屬于自己的單個“線程”中執行,而多個Actor并發執行。
3、Hello Theron
在談及更多內容之前,我們先來看看Theron的一個簡單的范例,借以獲得一個最直觀的印象,Theron提供了makefile便于gcc用戶編譯,同時其也為Windows用戶提供了Visual Studio solution文件Theron.sln用于構建Theron。
編譯Theron很容易,不會有太多的障礙,需要注意的是構建Theron需要指定依賴的線程庫,Theron支持三種線程庫:std::thread(C++11 標準線程庫)、Boost.Thread和Windows threads。
使用makefile構建時,通過threads參數指定使用的線程庫,使用Visual Studio構建時,通過選擇適當的Solution configuration來指定使用的線程庫,下面我們來看一個最簡單的范例:
#include
#include
#include
// 定義一個消息類型
// 在 Theron 中,任何類型都可以作為一個消息類型
// 唯一的一個約束是消息類型的變量能夠被拷貝的
// 消息按值發送(而非發送它們的地址)
struct StringMessage
{
char m_string[64];
};
// 用戶定義的 Actor 總需要繼承于 Theron::Actor
// 每個 Actor 和應用程序的其他部分通信的唯一途徑就是通過消息
class Actor : public Theron::Actor
{
public:
inline Actor()
{
// 注冊消息的處理函數
RegisterHandler(this, &Actor::Handler);
}
private:
// 消息處理函數的第一個參數指定了處理的消息的類型
inline void Handler(const StringMessage& message, const Theron::Address from)
{
printf("%sn", message.m_string);
if (!Send(message, from))
printf("Failed to send message to address %dn", from.AsInteger());
}
};
int main()
{
// Framework 對象用于管理 Actors
Theron::Framework framework;
// 通過 Framework 構建一個 Actor 實例并持有其引用
// Actor 的引用類似于 Java、C# 等語言中的引用的概念
// Theron::ActorRef 采用引用計數的方式實現,類似于 boost::shared_ptr
Theron::ActorRef simpleActor(framework.CreateActor());
// 創建一個Receiver用于接收Actor發送的消息
// 用于在非Actor代碼中(例如main函數中)與Actor通信
Theron::Receiver receiver;
// 構建消息
StringMessage message;
strcpy(message.m_string, "Hello Theron!");
// 通過 Actor 的地址,我們就可以向 Actor 發送消息了
if (!framework.Send(message, receiver.GetAddress(), simpleActor.GetAddress()))
printf("Failed to send message!n");
// 等到 Actor 發送消息,避免被關閉主線程
receiver.Wait();
return 0;
}
這個范例比較簡單,通過Actor輸出了Hello Theron,需要額外說明的一點是消息在Actor之間發送時會被拷貝,接收到消息的Actor只是引用到被發送消息的一份拷貝,這么做的目的在于避免引入共享內存、同步等問題。
Theron的消息處理前面談到過,每個Actor都工作在一個屬于自己的“線程”上,我們通過一個例子來認識這一點,我們修改上面例子中的Actor:: Handler成員方法:
inline void Handler(const StringMessage& message, const Theron::Address from)
{
while (true)
{
printf("%s --- %dn", message.m_string, GetAddress().AsInteger());
#ifdef _MSC_VER
Sleep(1000);
#else
sleep(1);
#endif
}
}
此Handler會不斷的打印message并且帶上當前Actor的地址信息,在main函數中,我們構建兩個Actor實例并通過消息喚醒它們,再觀察輸出結果:
Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 2
Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 1
......
這和我們預期的一樣,兩個Actor實例在不同的線程下工作,實際上,Framework創建的時候會創建系統級的線程,默認情況下會創建兩個(可以通過 Theron::Framework 構造函數的參數決定創建線程的數量),它們構成一個線程池,我們可以根據實際的CPU核心數來決定創建線程的數量,以確保CPU被充分利用。
那么,線程池的線程是以何種方式進行調度的呢?如下圖所示:
接收到消息的Actor會被放置于一個線程安全的Work隊列中,此隊列中的Actor會被喚醒的工作線程取出,并進行消息的處理,這個過程中有兩個需要注意的地方:
(1)、對于某個Actor我們可以為某個消息類型注冊多個消息處理函數,那么,此消息類型對應的多個消息處理函數會按照注冊的順序被串行執行下去。
(2)、線程按順序處理Actor收到的消息,一個消息未處理完成不會處理消息隊列中的下一個消息我們可以想象,如果存在三個Actor,其中兩個Actor的消息處理函數中存在死循環(例如上例中的while(true)),那么它們一旦執行就會霸占兩條線程,若線程池中沒有多余線程,那么另一個Actor將被“餓死”(永遠得不到執行)。
我們可以在設計上避免這種 Actor 的出現,當然也可以適當的調整線程池的大小來解決此問題,Theron中,線程池中線程的數量是可以動態控制的,線程利用率也可以測量,但是務必注意的是,過多的線程必然導致過大的線程上下文切換開銷。
4、一個詳細的例子
我們再來看一個詳細的例子,借此了解Theron帶來的便利,生產者消費者的問題是一個經典的線程同步問題,我們來看看Theron如何解決這個問題:
#include
#include
#include
const int PRODUCE_NUM = 5;
class Producer : public Theron::Actor
{
public:
inline Producer(): m_item(0)
{
RegisterHandler(this, &Producer::Produce);
}
private:
// 生產者生產物品
inline void Produce(const int& /* message */, const Theron::Address from)
{
int count(PRODUCE_NUM);
while (count--)
{
// 模擬一個生產的時間
#ifdef _MSC_VER
Sleep(1000);
#else
sleep(1);
#endif
printf("Produce item %dn", m_item);
if (!Send(m_item, from))
printf("Failed to send message!n");
++m_item;
}
}
// 當前生產的物品編號
int m_item;
};
class Consumer : public Theron::Actor
{
public:
inline Consumer(): m_consumeNum(PRODUCE_NUM)
{
RegisterHandler(this, &Consumer::Consume);
}
private:
inline void Consume(const int& item, const Theron::Address from)
{
// 模擬一個消費的時間
#ifdef _MSC_VER
Sleep(2000);
#else
sleep(2);
#endif
printf("Consume item %dn", item);
--m_consumeNum;
// 沒有物品可以消費請求生產者進行生產
if (m_consumeNum == 0)
{
if (!Send(0, from))
printf("Failed to send message!n");
m_consumeNum = PRODUCE_NUM;
}
}
int m_consumeNum;
};
int main()
{
Theron::Framework framework;
Theron::ActorRef producer(framework.CreateActor());
Theron::ActorRef consumer(framework.CreateActor());
if (!framework.Send(0, consumer.GetAddress(), producer.GetAddress()))
printf("Failed to send message!n");
// 這里使用 Sleep 來避免主線程結束
// 這樣做只是為了簡單而并不特別合理
// 在實際的編寫中,我們應該使用Receiver
#ifdef _MSC_VER
Sleep(100000);
#else
sleep(100);
#endif
return 0;
}
生產者生產物品,消費者消費物品,它們并行進行,我們沒有編寫創建線程的代碼,沒有構建共享內存,也沒有處理線程的同步,這一切都很輕松的完成了。
5、代價和設計
和傳統的多線程程序相比Theron有不少優勢,通過使用Actor,程序能夠自動的并行執行,而無需開發者費心,Actor總是利用消息進行通信,消息必須拷貝,這也意味著我們必須注意到,在利用Actor進行并行運算的同時需避免大量消息拷貝帶來的額外開銷。
Actor模型強調了一切皆為Actor,這自然可以作為我們使用Theron的一個準則,但過多的Actor存在必然導致Actor間頻繁的通信。
適當的使用Actor并且結合Object模型也許會是一個不錯的選擇,例如,我們可以對系統進行適當劃分,得到一些功能相對獨立的模塊,每個模塊為一個Actor,模塊內部依然使用Object模型,模塊間通過Actor的消息機制進行通信。
馬海祥博客點評:
Theron是個有趣的東西,也許你沒有使用過它,你也不了解Actor模型,但是Actor的思想卻不新鮮,甚至你可能正在使用,目前來說,我還沒有找到Theron在哪個實際的商業項目中使用,因此對Theron的使用還存在一些未知的因素。
還有一些特性,諸如跨主機的分布式的并行執行是Theron不支持的,這些都限制了Theron的使用,不過我也正在積極的改變一些東西,無論Theron未來如何,Theron以及Actor模型帶來的思想會讓我們更加從容面對多核的挑戰。
本文發布于馬海祥博客文章,如想轉載,請注明原文網址摘自于http://www.mahaixiang.cn/bcyy/954.html,注明出處;否則,禁止轉載;謝謝配合!
總結
以上是生活随笔為你收集整理的java actor模型实例,详解Theron通过Actor模型解决C++并发编程的一种思维的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vb调用matlab工具箱,Matlab
- 下一篇: java list移除所有元素,从Lis