任务和主动对象(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搭配来实现主动对象模式.