主動對象模式用於降低方法執行和方法調用之間的耦合。該模式描述了另外一種更爲透明的任務間通信方法。
傳統上,所有的對象都是被動的代碼段,對象中的代碼是在對它發出方法調用的線程中執行的,當方法被調用時,調用線程將阻塞,直至調用結束。而主動對象卻不一樣。這些對象具有自己的命令執行線程,主動對象的方法將在自己的執行線程中執行,不會阻塞調用方法。
例如,設想對象"A"已在你的程序的main()函數中被實例化。當你的程序啓動時,OS創建一個線程,以從main()函數開始執行。如果你調用對象A的任何方法,該線程將"流過"那個方法,並執行其中的代碼。一旦執行完成,該線程返回調用該方法的點並繼續它的執行。但是,如果"A"是主動對象,事情就不是這樣了。在這種情況下,主線程不會被主動對象借用。相反,當"A"的方法被調用時,方法的執行發生在主動對象持有的線程中。另一種思考方法:如果調用的是被動對象的方法(常規對象),調用會阻塞(同步的);而另一方面,如果調用的是主動對象的方法,調用不會阻塞(異步的)。
由於主動對象的方法調用不會阻塞,這樣就提高了系統響應速度,在網絡編程中是大有用武之地的。
在這裏我們將一個"Logger"(日誌記錄器)對象對象爲例來介紹如何將一個傳統對象改造爲主動對象,從而提高系統響應速度。
Logger的功能是將一些系統事件的記錄在存儲器上以備查詢,由於Logger使用慢速的I/O系統來記錄發送給它的消息,因此對Logger的操作將會導致系統長時間的等待。
其功能代碼簡化如下:
- class Logger: public ACE_Task<ACE_MT_SYNCH>
- {
- public:
- void LogMsg(const string& msg)
- {
- cout<<endl<<msg<<endl;
- ACE_OS::sleep(2);
- }
- };
爲了實現實現記錄日誌操作的主動執行,我們需要用命令模式將其封裝,從而使得記錄日誌的方法能在合適的時間和地方主動執行,封裝方式如下:
- class LogMsgCmd: public ACE_Method_Object
- {
- public:
- LogMsgCmd(Logger *plog,const string& msg)
- {
- this->log=plog;
- this->msg=msg;
- }
- int call()
- {
- this->log->LogMsg(msg);
- return 0;
- }
- private:
- Logger *log;
- string msg;
- };
- class Logger: public ACE_Task<ACE_MT_SYNCH>
- {
- public:
- void LogMsg(const string& msg)
- {
- cout<<endl<<msg<<endl;
- ACE_OS::sleep(2);
- }
- LogMsgCmd *LogMsgActive(const string& msg)
- {
- new LogMsgCmd(this,msg);
- }
- };
這裏對代碼功能做一下簡單的說明:
ACE_Method_Object是ACE提供的命令模式藉口,命令接口調用函數爲int call(),在這裏通過它可以把每個操作日誌的調用封裝爲一個LogMsgCmd對象,這樣,當原來需要調用LogMsg的方法的地方只要調用LogMsgActive即可生成一個LogMsgCmd對象,由於調用LogMsgActive方法,只是對命令進行了封裝,並沒有進行日誌操作,所以該方法會立即返回。然後再新開一個線程,將LogMsgCmd對象作爲參數傳入,在該線程中執行LogMsgCmd對象的call方法,從而實現無阻塞調用。
然而,每次對一個LogMsg調用都開啓一個新線程,無疑是對資源的一種浪費,實際上我們往往將生成的LogMsgCmd對象插入一個命令隊列中,只新開一個命令執行線程依次執行命令隊列中的所有命令。並且,爲了實現對象的封裝,命令隊列和命令執行線程往往也封裝到Logger對象中,代碼如下所示:
- #include "ace/OS.h"
- #include "ace/Task.h"
- #include "ace/Method_Object.h"
- #include "ace/Activation_Queue.h"
- #include "ace/Auto_Ptr.h"
- #include <string>
- #include <iostream>
- using namespace std;
- class Logger: public ACE_Task<ACE_MT_SYNCH>
- {
- public:
- Logger()
- {
- this->activate();
- }
- int svc();
- void LogMsg(const string& msg);
- void LogMsgActive (const string& msg);
- private:
- ACE_Activation_Queue cmdQueue; //命令隊列
- };
- class LogMsgCmd: public ACE_Method_Object
- {
- public:
- LogMsgCmd(Logger *plog,const string& msg)
- {
- this->log=plog;
- this->msg=msg;
- }
- int call()
- {
- this->log->LogMsg(msg);
- return 0;
- }
- private:
- Logger *log;
- string msg;
- };
- void Logger::LogMsg(const string& msg)
- {
- cout<<endl<<msg<<endl;
- ACE_OS::sleep(2);
- }
- //以主動的方式記錄日誌
- void Logger::LogMsgActive(const string& msg)
- {
- //生成命令對象,插入到命令隊列中
- cmdQueue.enqueue(new LogMsgCmd(this,msg));
- }
- int Logger::svc()
- {
- while(true)
- {
- //遍歷命令隊列,執行命令
- auto_ptr<ACE_Method_Object> mo
- (this->cmdQueue.dequeue ());
- if (mo->call () == -1)
- break;
- }
- return 0;
- }
- int main (int argc, ACE_TCHAR *argv[])
- {
- Logger log;
- log. LogMsgActive ("hello");
- ACE_OS::sleep(1);
- log.LogMsgActive("abcd");
- while(true)
- ACE_OS::sleep(1);
- return 0;
- }
在這裏需要注意一下命令隊列ACE_Activation_Queue對象,它是線程安全的,使用方法比較簡單,這裏我也不多介紹了。
主動對象的基本結構就是這樣,然而,由於主動對象是異步調用的,又引出瞭如下兩個新問題:
- 方法調用線程如何知道該方法已經執行完成?
- 如何或得方法的返回值?
這兩個問題將在下回給與解決。