. 任務和主動對象(Active Object): 併發編程模式 - ACE程序員教程2008-12-07 13:52
每個ACE_Task對象都包含一個或多個線程和一個底層的消息隊列.
任務之間可以是通過這些消息隊列進行通信. (用putq()來插入用getq()來提取.)
它做好了框架. 我們只要在派生的實際任務類中提供那些用以回調的模板方法就可以了.
5.2.2 創建和使用任務(ACE_Task).
要使用ACE_Task. 先從它派生一個子類. 然後:
實現初始化和終止方法: 即open()和close().
調用"啓用"方法: 即activate(). 調用它之後. 會創建一定數量(由參數指定)的線程. 而這些線程的函數就是svc().
實現svc().
///////////////////////////////////////////
// 例子. 用ACE_Task來實現 生產者-消費者.
// 演示了怎麼用ACE_Task類中的消息隊列實現任務之間的通信.
// 來自 <<ace程序員教程>> 59頁
///////////////////////////////////////////
// 消費者
#include "ace/os.h"
#include "ace/Task.h"
#include "ace/Message_Block.h"
//消費者
class Consumer : public ACE_Task<ACE_MT_SYNCH> { //從ACE_Task派生
public:
int open(void*) {
ACE_DEBUG((LM_DEBUG, "(%t) Producer task opend/n"));
activate(THR_NEW_LWP, 1); // 必須調用該成員函數. 不明白既然要這樣. 怎麼不把activate和open的功能用一個函數呢?
return 0;
}
int svc(void) { //實際的線程函數. 在activate函數被調用後就自動創建線程來執行這個函數.
ACE_Message_Block * mb = 0;
do {
mb = 0;
getq(mb); //從消息隊列中取出一個消息到mb. 若當前的消息隊列中沒有消息. 則阻塞.
ACE_DEBUG((LM_DEBUG, "(%t)Got message: %d from remote task/n", *mb->rd_ptr()));
} while(*mb->rd_ptr() < 10);
return 0;
}
int close(u_long) { //在任務完成後自動執行的一個函數.
ACE_DEBUG((LM_DEBUG, "Consumer closes down /n" ));
return 0;
}
}
///////////////////////////////////////
// 生產者的任務類
///////////////////////////////////////
class Producer : public ACE_Task<ACE_MT_SYNCH> {
public:
Producer(Consumer* consumer) : consumer_(consumer), data_(0) {
mb = new ACE_Message_Block((char*)&data_, sizeof(data_)); // 這個new出來的消息不需要刪除麼??????
}
int open(void*){
ACE_DEBUG((LM_DEBUG, "(%t) Producer task opened /n"));
activate(THR_NEW_LWP, 1); //創建生產者線程. 線程函數是svc();
return 0;
}
int svc() {
while (data_<11) {
ACE_DEBUG((LM_DEBUG, "(%t)Sending message: %d to remote task /n", data_));
consumer_->putq(mb_); //向消費者的消息隊列中放入一個消息.
ACE_OS::sleep(1);
data_++;
}
return 0;
}
int close(u_long){
ACE_DEBUG((LM_DEBUG, "Producer closes down /n"));
return 0;
}
private:
char data_;
Consumer * consumer_; //通過持有的消費者指針. 給消費者發送消息.
ACE_Message_Block * mb_; //要發送的消息.
};
int main(int argc, char* argv[]) {
Consumer * consumer = new Consumer;
Producer * producer = new Producer(consumer);
producer->open(0);
consumer->open(0);
}
5.3 主動對象模式
什麼是主動對象?
先說說普通的對象. 我們調用它的方法時. 這些方法是在調用它的線程中執行的.
但主動對象則擁有自己的線程. 只要告訴主動對象該做什麼就行了. 它會在自己的線程中處理. 而不是在主線程中(即調用是異步進行的).
使用主動對象模式. 可以對需要多線程的程序換一種更加面向對象的思路.
在實現主動模式時. 可以用下邊介紹的ACE_Task(任務)來實現. 大致過程是:
在任務對象中保存一個"事件隊列". 隊列中是一些事件對象(就是使用了命令(Command)模式的一些對象).
因爲這個隊列是一個阻塞隊列. 並且是線程安全的. 所以其它線程可以向該對象的事件隊列中放入各種事件.
然後在重寫的 ACE_Task::svc() 函數中用一個循環. 不斷的從事件隊列中取出各種事件並執行之.....
所以一般上. 主動對象模式的參與者就是如下幾個部分:
主動對象(派生於ACE_Task)
ACE_Activation_Queue (事件隊列)
一些 ACE_Method_Object(函數對象, 就是要放入事件隊列的事件了)
一些 ACE_Future對象(用於保存函數對象的返回值)
在ACE中具體的使用主動對象的步驟:
從ACE_Task派生一個主動對象類.
本類需要維持一個ACE_Activation_Queue對象. 並在svc()函數裏從其中取出事件並執行之.
對所有的要從客戶異步調用的函數. 都要編寫一個相應的函數對象類(繼承自ACE_Method_Object. 實現其call()方法 ).
然後在該函數中創建該函數對象類的實例. 將其加入ACE_Activation_Queue隊列中等待執行.
這樣客戶使用時只要調用該類的方法就行了(一切在內部都被變成了異步的). 調用會返回一個 ACE_Future<> (期貨對象).
它是異步操作的結果(主動對象實際處理該事件後會設置該期貨對象的值).
客戶可以從中取出結果. 但如果當前期貨的值尚未被設置. 取出結果的操作會阻塞. 也可以用ready()函數來檢查結果是否已被設置.
///////////////////////////////////////////
// 一個使用主動對象模式的例子
// 來自 <<ace程序員教程>> 61頁
// 功能: 日誌記錄的類Logger 是一個主動對象類. 客戶在main()函數中對它的log_Msg()調用會被轉換爲異步的進行.
///////////////////////////////////////////
class Logger : public ACE_Task<ACE_MT_SYNCH> { // Logger是 主動對象的類.
// 下邊是幾個模板方法.
virtual int open(void*) {
return activate(THR_NEW_LWP);
}
// 書上下行代碼好像不對: 重新指定了虛函數的默認參數值. 如果是用基類的引用調用時. 豈不是要出問題?
virtual int close(u_long flags = 0) { return 0; }
// 這個 svc 函數中實現主動對象的內部線程中要做的事.
// 即取出事件隊列中的事件. 並執行.
virtual int svc() {
while(1) {
// 從事件隊列中取出一個事件對象
auto_ptr<ACE_Method_Object> mo(this->activation_queue_.dequeue()); //若當前事件隊列中沒有事件. 則阻塞.
if (mo->call() = -1) break; // 執行該事件 返回-1表示出錯.
//但這樣break出去後. activation_queue_中剩餘的函數對象的內存誰來delete?
//爲什麼不用boost::shared_ptr ?
}
return 0;
}
// 下邊是客戶希望異步調用的函數
// 客戶在調用該方法後. 會把對應的一個函數對象插入事件隊列中.
// 並返回一個 ACE_Future<> (期貨對象). 在主動對象實際處理該事件後. 會設置該期貨對象.
// 它是異步操作的結果. 客戶可以從中取出結果. 但如果當前期貨的值尚未被設置. 取出結果的操作會阻塞.
// 所以可以用 ready() 這樣的函數來檢查結果是否以被設置.
ACE_Future<u_long> logMsg(const char * msg) {
ACE_Future<u_long> resultant_future;
//構造一個函數對象. 並插入"事件隊列. 這裏的函數對象類是 LogMsg類. 該類的定義在後邊給出.
activation_queue_.enqueue(new LogMsg_MO(this, msg, resultant_future)); //new出的函數對象.在svc中釋放.
return resultant_future; //客戶在調用該方法後就得到這個期貨對象
}
private:
// 實際的 做實用工作 的函數(用來實際的寫入日誌). 客戶不需要知道它.
u_long logMsg_i(const char* msg) {
// 實際的將 msg 寫入日誌........
ACE_OS::sleep(2); // 延時(就當是正在寫磁盤....).
return 10;
}
private:
ACE_Activation_Queue activation_queue_; //主動對象內部需要維持這麼一個"事件隊列"
};
// 上邊的類中用的一個 函數對象類.
// 由於在上邊的類中的事件隊列 activation_queue_ 只能接受ACE_Method_Object對象. 所以本類要從它繼承.
class LogMsg_MO : public ACE_Method_Object {
public:
LogMsg_MO(Logger* logger, const char* msg, ACE_Future<u_long>& future_result)
: logger_(logger), msg_(msg), future_result_(future_result)
{}
virtual ~LogMsg_MO() {}
// 這個call方法在主動對象內部的線程中執行.
// 它做完工作(這裏是調用logger_->logMsg_i)後. 要set期貨
virtual int call () { // 必須重寫基類中聲明的這個方法. 注意這個方法的實現.
return future_result_.set(logger_->logMsg_i(msg_)); //調用期貨的set方法. 設置異步運算的結果.
//設置結果後. 客戶線程中就可以取出該結果了.
}
private:
Logger * logger_;
const char* msg_;
ACE_Future<u_long> future_result_; //這種對象的生命期是怎麼管理的? 怎麼做到在線程之間傳遞的?
}
// 主動對象的類已經弄好了. 下邊演示怎麼使用它:
int main() ( int, char*[]) {
Logger* logger = new Logger;
logger->open(0); // 啓動主動對象內部的線程.
const char* msg = "this is msg";
ACE_Future<u_long> logresult = logger->logMsg(msg); //日誌記錄的操作被異步的進行.
//檢查異步計算是否已經結束
if (logresult.ready()) // 檢查logresult是否已經被set. 若是返回1. 否則返回0. 這個調用是非阻塞的.
;
else
;
// 取出異步計算的結果
u_long re;
logresult.get(re); // 若logresult尚未被set. 則get()會阻塞.
cout << re << endl;
ACE_Thread_Manager::instance() -> wait();
}
===============<完>=================
// 上邊的例子中的函數對象類. 太麻煩.
// 而且主動對象的事件隊列中存儲的是 函數對象的指針. 這些指針是new出來的原始指針. 要是忘了釋放或像例子中的svc()函數中
// 那樣沒有來得及釋放. 豈不是要內存泄漏啊.
// 所以想自己寫一個用Loki::Functor做元素的事件隊列. 來代替ACE_Activation_Queue. 其中保存用智能指針管理的函數對象指針.
// 再用它和ACE_Task搭配來實現主動對象模式.