APG例子的Active_Objects示例
md,硬着头皮上,分析一把这个只有100多行的程序。
遇到的问题列表:
ACE_TRACE的不起作用:在调试中并没有看到有关的ACE_TRACE的输出,经过GOOGLE,发现需要定义ACE_NTRACE 为0,这样就可以打开TRACE跟踪了。
ACE自动指针的问题:在看程序的时候有这样的一段代码:
在Scheduler类的svc函数中,有这样的代码:
auto_ptr<ACE_Method_Request> request (this->activation_queue_.dequeue());
恩,这是一个循环内部的代码,每次从activation_queue_中取得的块,都会在auto_ptr下次构造这个reqeust的时候被释放,或者最终销毁request的时候被释放。这个是auto_ptr的用处所在。自己不是特别习惯这样的用法。因为不是那样的直观,不过这样的用法确实能够避免很多问题。代价是一些不够透明的影响。
到底ACE_Future是一个什么样的东西?为了能够彻底解决这个疑惑,俺决定把ACE_Future拿出来看看。ACE_Future 是一个为了异步返回结果所设计的模版。他允许一个写,多个读。经过深入的分析,ACE_Future是一个用来装载结果引用的一个容器,如果引用的数量为0,那么就会销毁这个结果。这个比较拗口一点,具体来讲,就是这样的,我们可以定义一堆的ACE_Future的东西,这些东西可以互相指来指去,每指一次,那么就增加一个引用次数。当销毁这个ACE_Future的时候,如果引用次数不为0,那么这个对象实际上是没有销毁。并且对于写来说是有保护的。这样,可以很容易的写出很简单的代码。为了更加清楚地说明这点,我们用这个例子里的实际应用来说明:
在例子程序中,主程序定义了一个ACE_Future<int> results[10]的数组,用来保存工作线程的工作结果。在一般的设计中,要么使用把这10个数组元素传入工作线程中,要么这10个数组元素用来保存工作线程内部暴露的结果。可是,例子是怎样做的呢?
ACE_Future<int> results[10];
for(int i=0;i<10;i++)
results[i] = controller.status_update();
难道这个controller.status_update返回了线程内部的结果?我们看看这个status_update的实现。
ACE_Future<int> status_update()
{
ACE_Future<int> result;
This->scheduler_.enqueue(new StatusUpdate(this->controller_, result));
return result;
}
俺的脑海中,疑窦顿生,shit,这个不是摆明了说这里定义的result其实是跨越了生命周期的么,也就是说,这里定义的result,并不会随着函数的终止而销毁,而会带到调用者(实际上是有3个地方,一个是这里的result,一个是给StatusUpdate的result,还有一个,就是返回去的result)当这三个地方的result都销毁的时候,最后一个result销毁的时候发现自己的引用数量变成了0,于是销毁自己。这样就变相的延长了结果的生命周期。
不过这样也会引入一个问题,就是说,由于返回的时候,会为了返回值而重新创建一个ACE_Future<int> r=result;然后才会销毁这个result,然后调用者Results[j]=r。然后再销毁这个临时的返回变量r。
为了清晰的鉴定,俺作了一个函数来决定:
ACE_Future<int> stupid(void)
{
ACE_Future<int> result(5);
Return result;
}
调用者是这样的:
ACE_Future<int> rs;
Rs = stupid();
如果是这样,那么这个结果(在ACE_Future<int> result(5))定义的result),会在rs销毁的时候才真正销毁。
如果俺就直接调用stupid()而没有rs=stupid();那么这个结果在执行完stupid()之后就立刻销毁。
这个程序何时创建线程的?在HA_ControllerAgentProxy类中,有一个成员Scheduler scheduler_这个成员函数的构造函数中调用了activate(),这里创建的线程。并且开始从svc函数开始工作。
这几个问题解决以后,这个程序的结构就迎刃而解。这个程序是演示ACE的多线程任务调度的。主要是演示ACE_Method_Request和ACE_Task_Base之间的任务配合,以及使用ACE_Future作返回值传递的演示。还有关于auto_ptr的使用范例。
程序分析
根据传统的分析方法,我们从ACE_TMAIN主函数开始进行分析这个例子。
int ACE_TMAIN (int, ACE_TCHAR *[])
{
HA_ControllerAgentProxy controller;
ACE_Future<int> results[10];
for (int i = 0 ; i < 10; i++)
results[i] = controller.status_update ();
ACE_DEBUG ((LM_DEBUG,
ACE_TEXT ("begin sleep 5 seconds\n")));
ACE_OS::sleep (5); // Do other work.
ACE_DEBUG((LM_DEBUG,
ACE_TEXT("sleep end,and begin get results\n")));
// Get results...
for (int j = 0; j < 10; j++)
{
int result;
results[j].get (result);
ACE_DEBUG ((LM_DEBUG,
ACE_TEXT ("New status_update %d\n"), result));
}
// Cause the status_updater threads to exit.
controller.exit ();
ACE_Thread_Manager::instance ()->wait ();
return 0;
}
首先是定义了一个用于控制用的类,这个类中,会有一个任务调度器,这个任务调度器会创建一个线程。在这个类中,还有一个用于控制用的类对象,这个类对象就是用于每次花费两秒钟对返回值加一。不过一开始的时候,这个控制用的类对象并不会自行工作,而是等到工作线程接下来调用。在主程序定义了这个变量之后,就已经创建了用于工作的线程,并且开始等待工作队列上的任务对象。
接下来定义了10个返回值。并且用一个循环设置10个任务(更新任务)。在设置更新任务的同时,工作线程从工作队列上得到了这个任务(见svc函数),并且调用这个任务对象的call方法。调用call方法目前就是调用的是StatusUpdate类的call方法。执行StatusUpdate的call方法会执行到用于控制类里边的加一操作,并且设置对应任务对象的返回值。
同时,主线程会休眠5秒钟,在休眠完成以后,主线程会依次取得返回值,很快的(由于前边sleep了5秒钟,所以,应当有两个已经执行完毕,有了返回值),会出现两个返回值,以后的返回值就是成对出现的了。
最后,通过往任务队列里边压一个退出任务通知svc函数退出。然后用ACE_Thread_Manager来等待所有的线程都结束。
难点
在StatusUpdate类里边,构造函数的定义:
StatusUpdate(HA_ControllerAgent & controller,ACE_Future<int> &returnval):controller_(controller),returnVal_(returnval)
而我们看见:定义的成员变量controller_和returnVal_却并非都是引用定义:
一个是:HA_ControllerAgent & controller_;一个是ACE_Future<int> returnVal_;
我们就有了疑惑,为何定义有区别?是否参数设计上一定都是引用设计?
对于controller来说,是需要引用设计的,要不然就是指针设计,因为不同的StatusUpdate对象需要同一个controller控制对象,所以,需要引用设计或者指针设计,同样的,这就要求成员变量controller的定义是引用或者指针。
但是注意的是对于ACE_Future<int>这个参数的设计。由于这个对象可能是跨作用域的,所以,如果用引用的方式赋值,就会导致成员变量指向一个已经释放的对象,所以,不能用引用赋值,只能用直接赋值,通过ACE_Future<int>模板本身来进行对象的引用。所以在参数上,既可以是&也可以不是引用。只要赋值不是引用赋值就可以了。
执行细节分析
1. 执行HA_ControllerAgentProxy controller.
2. 执行ACE_Future<int> results[10];
3. 执行controller.status_update();
主线程
创建controller.scheduler对象
Scheduler.Schedule{activate();}
创建controller.controller_对象
初始化了计数器(用于返回值)
创建10个ACE_Future<int>用于返回值
执行第一个controller.status_update()
创建ACE_Future<int>返回值
创建StatusUpdate对象(使用刚创建的返回值和controller.controller_对象)
压入schedule的工作队列(这时候就发现为什么有必要Scheduler类继承一个压队列方法了)
设置第一个返回值=刚创建的返回值
执行第二个到第10个controller.status_update
等待5秒钟
。
。
。
。
。
。
子线程
线程创建了
开始等待activation_queue_的队列
(由于队列空,则一直等待)
。
。
。
。
。
。
。
接收到队列里边的StatusUpdate对象
执行StatusUpdate.Call()
设置controller_的返回值+1
等待2秒钟
。
。
。
输出返回值
接收到队列里边的StatusUpdate对象
执行StatusUpdate.Call()
设置controller_的返回值+1
等待2秒钟
主线程
继续等待中
。
。
。
。
执行等待一个返回结果
立刻返回了
执行等待第二个返回结果
立刻返回了
执行等待第三个返回结果
等待中
。
返回了
执行等待第四个返回结果
等待中
。
。
。
。
。
返回了
<依次类推>
执行controller.exit
压入schedule的工作队列一个退出对象。这个退出对象的Call方法只是简单返回一个-1
执行等待线程管理器本instance所有的线程结束。
。
程序结束了
子线程
等待2秒钟
。
。
输出返回值
执行StatusUpdate.Call()
设置返回值=controller_的返回值
设置controller_的返回值+1
等待2秒钟
。
。
。
输出返回值
执行StatusUpdate.Call()
设置返回值=controller_的返回值
设置controller_的返回值+1
等待2秒钟
。
。
。
输出返回值。
<依次类推>
。
。
。
。
。
接收到推出对象ExitMethod,执行ExitMethod.Call返回-1,于是从svc返回
。
线程终止了
小结
本例子的关键词:ACE_Future,ACE_Task_Base,ACE_Method_Request,auto_ptr
还不是很习惯这样的思维方式。不过已经渐渐有点明白了。