ACE的线程管理机制
一、线程的创建与管理
不同的操作系统下用c++进行过多线程编程时,不同操作系统API提供了相同或是相似的功能,但是它们的API的差别却极为悬殊。ACE_Thread提供了对不同OS的线程调用的简单包装,通过一个通用的接口进行处理线程创建、挂起、取消和删除等问题,为跨平台开发提供了支持。
1.1、线程入口函数
所有线程必须从一个指定的函数开始执行,该函数称为线程函数,它必须具有下列原型:
void* worker(void *arg) {}
该函数输入一个void *型的参数,可以在创建线程时传入。
1.2、线程基本操作
1.2.1、创建一个线程
一个进程的主线程是由操作系统自动生成,如果你要让一个主线程创建额外的线程,可以通过ACE_Thread::spawn()实现,该函数一般的使用方式如下:
ACE_thread_t threadId;
ACE_hthread_t threadHandle;
ACE_Thread::spawn(
(ACE_THR_FUNC)worker, //线程执行函数
NULL, //执行函数参数
THR_JOINABLE | THR_NEW_LWP,
&threadId,
&threadHandle
);
为了简化,也可以使用其默认参数,直接使用ACE_Thread::spawn((ACE_THR_FUNC)worker) 来创建一个worker的线程.另外,ACE还提供了ACE_Thread::spawn_n函数来创建多个线程。
1.2.2、终止线程
在线程函数体中ACE_Thread::exit()调用即可终止线程执行。
1.2.3、设定线程的相对优先级
当一个线程被首次创建时,它的优先级等同于它所属进程的优先级。一个线程的优先级是相对于其所属的进程的优先级而言的。可以通过调用ACE_Thread::setprio函数改变线程的相对优先级,该函数的调用方式如下:
ACE_Thread::setprio(threadHandle,ACE_DEFAULT_THREAD_PRIORITY)
1.2.4、挂起及恢复线程
挂起线程可以通过来实现,它能暂停一个线程的执行,其调用方式如下:
ACE_Thread::suspend(threadHandle) 。
相应的,可以通过ACE_Thread::resume(threadHandle)恢复被挂起的线程的执行。
1.2.5、等待线程结束
在主函数中调用ACE_Thread::join(threadHandle)可阻塞主函数,直道线程结束才能继续执行。
1.2.6、停止线程
在主函数中调用ACE_Thread::cancel(threadHandle)可停止线程的执行(在Unix底下可以,而在windows下好像不起作用,有待检验)。
1.2.4、线程操作示例
#include "ace/Thread.h" #include "ace/Synch.h"
#include <iostream> using namespace std;
void* worker(void *arg) { for(int i=0;i<10;i++) { ACE_OS::sleep(1); cout<<endl<<"hello world"<<endl; } return NULL; }
int main(int argc, char *argv[]) { ACE_thread_t threadId; ACE_hthread_t threadHandle;
ACE_Thread::spawn( (ACE_THR_FUNC)worker, //线程执行函数 NULL, //执行函数参数 THR_JOINABLE | THR_NEW_LWP, &threadId, &threadHandle );
ACE_Thread::join(threadHandle);
return 0; } |
1.2、面向对象的线程封装
在上一节中,线程的操作使用ACE_Thread包装,但是线程操作还是面向过程的函数操作,而ACE_Task_Base对常用线程处理进行了面向对象包装,通过ACE_Task_Base,能对线程进行更好的操作。
要创建任务,需要进行以下步骤:
1.2.1、实现服务初始化和终止方法
open()方法应该包含所有专属于任务的初始化代码。其中可能包括诸如连接控制块、锁和内存这样的资源。
close()方法是相应的终止方法。
1.2.2、调用启用(activation)方法
在主动对象实例化后,你必须通过调用activate()启用它。要在主动对象中创建的线程的数目,以及其他一些参数,被传递给activate()方法。activate()方法会使svc()方法成为所有它生成的线程的启动点。该方法的声明如下:
virtual int activate (long flags = THR_NEW_LWP | THR_JOINABLE |THR_INHERIT_SCHED,
int n_threads = 1,
int force_active =0,
long priority =ACE_DEFAULT_THREAD_PRIORITY,
int grp_id = -1,
ACE_Task_Base *task =0,
ACE_hthread_tthread_handles[] = 0,
void *stack[] = 0,
size_t stack_size[] =0,
ACE_thread_tthread_ids[] = 0,
const char* thr_name[]= 0);
第一个参数线程创建标志flags可为如下组合:
THR_CANCEL_DISABLE : 不允许这个线程被取消;
THR_CANCEL_ENABLE : 允许这个线程被取消;
THR_CANCEL_DEFERRED: 只允许延迟的取消;
THR_BOUND : 创建一个线程,并绑定到一个可由内核调度的实体上;
THR_NEW_LWP : 创建一个内核级线程;该标志影响进程的并发属性;对"未绑定线程"来说,其预想的并发级别是增1,也就是添加一个新的内核线程到可用的线程池中,以运行用 户线程;在不支持N:M混合线程模型的OS平台上,该标志会被忽略;
THR_DETACHED : 创建一个分离的线程;这就意味着这个线程的退出状态不能被其它线程访问;当这个线程退出的时候,其线程ID和它所占用的资源会被OS自动回收;
THR_JOINABLE : 允许新创建的线程被"会合(join)";这就意味着这个线程的退出状态能够被其它线程访问,可以通过join()方法来访问它的退出状态,但是它的线程 ID和它所占用的资源不会被OS回收;所有ACE线程创建方法的默认行为都是THR_JOINABLE;
THR_SUSPENDED : 创建一个线程,但让其处在挂起状态;
THR_DAEMON : 创建一个看守(daemon)线程;
THR_SCHED_FIFO : 如果可用,使用FIFO政策调度新创建的线程;
THR_SCHED_RR : 如果可用,使用round-robin方案调度新创建的线程;
THR_SCHED_DEFAULT : 使用操作系统上可用的无论哪种默认调度方案;
THR_SCOPE_SYSTEM : 新线程在系统调度争用空间中创建,永久绑定于新创建的内核线程;
THR_SCOPE_PROCESS : 新线程在进程调度争用空间中创建,也就是说,它将作为一个用户线程运行;
线程的这些属性标志通过逻辑或运算符"|"串在一起,并把它作为ACE_Task_Base::active()方法的第一个参数传递给该方法。
1.2.3、实现任务专有的处理方法
如上面所提到的,在主动对象被启用后,各个新线程在svc()方法中启动。应用开发者必须在子类中定义和实现svc()方法。
1.2.4、一个简单的示例
// main.cpp : 定义控制台应用程序的入口点。 #include "ace/Task.h" #pragma comment(lib,"ACEd.lib")
class MyThreadTask : public ACE_Task_Base { public: virtual int open (void *args = 0) { return activate(); }
virtual int svc() { int i=0; while(i++<1000) { ACE_DEBUG((LM_DEBUG, "thread is running\n")); }
return 1; }
virtual int close(void) { ACE_DEBUG((LM_DEBUG, "thread closes down \n")); return 0; } };
int main(int argc, char* argv[]) { MyThreadTask mytask; mytask.open(); //启动线程
mytask.wait(); //等待线程停止
ACE_DEBUG((LM_DEBUG, "thread over! \n"));
return 0; } |
二、线程间通信
ACE_Task继承自ACE_Task_Base,为进程间的通信提供了一种基于消息的编程模式。ACE 中的每个任务(ACE_Task)都有一个底层消息队列(ACE_Message_Queue)。这个消息队列被用作任务间通信的一种方法。当一个任务想要与另一任务“谈话”时,它创建一个消息,并将此消息放入putq()到它想要与之谈话的任务的消息队列。接收任务通常用 getq()从消息队列里获取消息。
ACE_Task的消息队列可以由多个处理线程共享使用,所以需要提供同步模式,例如ACE_MT_SYNCH和ACE_NULL_SYNCH分别表示基于多线程的同步和不使用同步,这个参数是ACE_Task的一个模板参数。
例如:
1)消息的处理
class My_Task : public ACE_Task<ACE_MT_SYNCH>
{
public:
virtual int svc()
{
ACE_Message_Block* msg;
while(-1!=getq(msg))
{
// 在这里处理接收到的消息msg
//...............................
//回复消息
reply(msg);
}
}
};
ACE_Task::getq在当前消息队列没有消息时都会阻塞到有消息可用为止。
2)线程的开启
假设My_Task是一个基于ACE_Task的类,创建一个唯一的My_Task实例,这个可以通过
typedef ACE_Singleton<MyTask, SYNCH_METHOD> MYTASK;
在适当位置(一般是程序开始的时候),让My_Task开始工作
MYTASK::intance()->activate(THR_NEW_LWP | THR_JOINABLE|THR_INHERIT_SCHED , 5,);//开启5个线程
3)消息的发送
在有消息发生的时候发送消息
ACE_Message_Block * msg;
//fill the msg
MYTASK::intance()->putq(msg);
ACE_Task::putq只是完成将消息插入到消息队列的工作,理论上它应该立刻返回,但实际上,ACE_Task的消息队列有容量大小限制,这个限制由我们自己限定,当当前消息队列满时,ACE_Task::putq将阻塞一直到可以插入为止。
消息的类型与消息中所含有的数据请参见ACE_Message_Block类的声明。