ACE例子之APG例子的Active_Objects示例

类别:编程语言 点击:0 评论:0 推荐:
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

还不是很习惯这样的思维方式。不过已经渐渐有点明白了。

本文地址:http://com.8s8s.com/it/it23737.htm