ACE
拥有许多不同的用于创建和管理多线程程序的类。在这一章里,我们将查看ACE中的一些线程管理机制。在一开始,我们将查看那些简单的线程包装类,它们的管理功能很少。但是,随着内容的进展,我们将查看ACE_Thread_Manager中的更为强大的管理机制。ACE还拥有一组非常全面的处理线程同步的类。这些类也将在本章讲述。创建和取消线程
在不同的平台上,有着若干不同的用于线程管理的接口。其中包括
POSIX pthreads接口、Solaris线程、Win32线程等等。这些接口提供了相同或是相似的功能,但是它们的API的差别却极为悬殊。这就导致了困难、麻烦和易错的编程,因为应用程序员必须熟悉不同平台上的若干接口。而且,这样写下的程序,是不可移植和不灵活的。ACE_Thread
提供了对OS的线程调用的简单包装,这些调用处理线程创建、挂起、取消和删除等问题。它提供给应用程序员一个简单易用的接口,可以在不同的线程API间移植。ACE_Thread是非常“瘦”的包装,有着很少的开销。其大多数方法都是内联的,因而等价于对底层OS专有线程接口的直接调用。ACE_Thread中的所有方法都是静态的,而且该类一般不进行实例化。下面的例子演示怎样使用
ACE_Thread包装类创建、生成和联接(join)线程。
例
4-1#include "ace/Thread.h"
#include "ace/Synch.h"
static int number=0;
static int seed = 0;
static void* worker(void *arg)
{
ACE_UNUSED_ARG(arg);
ACE_DEBUG((LM_DEBUG,"Thread (%t) Created to do some work"));
::number++;
ACE_DEBUG((LM_DEBUG," and number is %d\n",::number));
//Let the other guy go while I fall asleep for a random period
//of time
ACE_OS::sleep(ACE_OS::rand()%2);
//Exiting now
ACE_DEBUG((LM_DEBUG,
"\t\t Thread (%t) Done! \t The number is now: %d\n",number));
return 0;
}
int main(int argc, char *argv[])
{
if(argc<2)
{
ACE_DEBUG((LM_DEBUG,"Usage: %s
\n", argv[0])); ACE_OS::exit(1);
}
ACE_OS::srand(::seed);
//setup the random number generator
int n_threads= ACE_OS::atoi(argv[1]);
//number of threads to spawn
ACE_thread_t *threadID = new ACE_thread_t[n_threads+1];
ACE_hthread_t *threadHandles = new ACE_hthread_t[n_threads+1];
if(ACE_Thread::spawn_n(threadID, //id’s for each of the threads
n_threads, //number of threads to spawn
(ACE_THR_FUNC)worker, //entry point for new thread
0, //args to worker
THR_JOINABLE | THR_NEW_LWP, //flags
ACE_DEFAULT_THREAD_PRIORITY,
0, 0, threadHandles)==-1)
ACE_DEBUG((LM_DEBUG,"Error in spawning thread\n"));
//spawn n_threads
for(int i=0; i ACE_Thread::join(threadHandles[i]); //Wait for all the threads to exit before you let the main fall through //and have the process exit. return 0; }
在这个简单的例子中,创建了
n_thread个工作者线程。每个线程都执行程序中定义的worker()函数。线程是通过使用ACE_Thread::spawn_n()调用创建的。要作为线程的执行启动点调用的函数的指针(在此例中为worker()函数)被作为参数传入该调用中。要注意的重点是ACE_Thread::spawn_n()要求所有的线程启动函数(方法)必须是静态的或全局的(就如同直接使用OS线程API时所要求的一样)。一旦工作者函数启动,它将全局变量
number的值加1,报告它的当前值,然后进入休眠状态,以把处理器让给其他线程。sleep()休眠一段随机长度的时间。在线程醒来后,它将number的当前值通知给用户,然后退出worker()函数。一旦线程从它的启动函数返回,它在线程库上隐含地发出线程
exit()调用并退出。这样一旦“掉出”worker()函数,工作者线程也就退出了。负责创建工作者线程的主线程,在退出之前“等待”所有其他的线程完成它们的执行并退出。当主线程退出时(通过退出main()函数),整个进程也将被销毁。这之所以会发生是因为每当线程退出main()函数时,都会隐含地调用exit(3c)函数。因此,如果主线程没有被强制等待其他线程结束,当它死掉时,进程将会被自动销毁,并在它的所有工作者线程完成工作之前销毁它们!上面所说的等待是通过使用
ACE_Thread::join()调用来完成的。该方法的参数是你想要主线程与之联接的线程的句柄(ACE_hthread_t)。在此例中有若干事实值得注意。首先,在该类中没有可供我们调用的管理功能,用以在内部记住应用所派生的线程的
ID。这使得我们难以联接(join())、杀死(kill())或是一般性地管理我们派生的线程。在本章后面讲述的ACE_Thread_Manager缓解了这些问题,一般说来,应该使用ACE_Thread_Manager而不是线程包装API。其次,在程序中没有使用同步原语来保护全局数据。在此例中,它们并不是必须的,因为所有的线程都只对全局变量执行一次加操作。但是在实际应用中,为了保护所有共享互斥数据(全局或静态变量),比如说全局的
number变量,“锁”将会是必需的。同步原语
ACE
有若干可用于同步目的的类。这些类可划分为以下范畴:
- ACE Lock
4.2.1 ACE Lock类属
锁类属包含的类包装简单的锁定机制,比如互斥体、信号量、读/写互斥体和令牌。在这一类属中可用的类在表
4-1中显示。每个类名后都有对用法和用途的简要描述:
名字 | 描述 |
ACE_Mutex | 封装互斥机制(根据平台,可以是 mutex_t、pthread_mutex_t等等)的包装类,用于提供简单而有效的机制来使对共享资源的访问序列化。它与二元信号量(binary semaphore)的功能相类似。可被用于线程和进程间的互斥。 |
ACE_Thread_Mutex | 可用于替换 ACE_Mutex,专用于线程同步。 |
ACE_Process_Mutex | 可用于替换 ACE_Mutex,专用于进程同步。 |
ACE_NULL_Mutex | 提供了 ACE_Mutex接口的“无为”(do-nothing)实现,可在不需要同步时用作替换。 |
ACE_RW_Mutex | 封装读者/作者锁的包装类。它们是分别为读和写进行获取的锁,在没有作者在写的时候,多个读者可以同时进行读取。 |
ACE_RW_Thread_Mutex | 可用于替换 ACE_RW_Mutex,专用于线程同步。 |
ACE_RW_Process_Mutex | 可用于替换 ACE_RW_Mutex,专用于进程同步。 |
ACE_Semaphore | 这些类实现计数信号量,在有固定数量的线程可以同时访问一个资源时很有用。在 OS不提供这种同步机制的情况下,可通过互斥体来进行模拟。 |
ACE_Thread_Semaphore | 应被用于替换 ACE_Semaphore,专用于线程同步。 |
ACE_Process_Semaphore | 应被用于替换 ACE_Semaphore,专用于进程同步。 |
ACE_Token | 提供“递归互斥体”( recursive mutex),也就是,当前持有某令牌的线程可以多次重新获取它,而不会阻塞。而且,当令牌被释放时,它确保下一个正阻塞并等待此令牌的线程就是下一个被放行的线程。 |
ACE_Null_Token | 令牌接口的“无为”( do-nothing)实现,在你知道不会出现多个线程时使用。 |
ACE_Lock | 定义锁定接口的接口类。一个纯虚类,如果使用的话,必须承受虚函数调用开销。 |
ACE_Lock_Adapter | 基于模板的适配器,允许将前面提到的任意一种锁定机制适配到 ACE_Lock接口。 |
表
4-1 ACE锁类属中的类
