表
4-1中描述的类都支持同样的接口。但是,在任何继承层次中,这些类都是互不关联的。在ACE中,锁通常用模板来参数化,因为,在大多数情况下,使用虚函数调用的开销都是不可接受的。使用模板使得程序员可获得相当程度的灵活性。他可以在编译时(但不是在运行时)选择他想要使用的的锁定机制的类型。然而,在某些情形中,程序员仍可能需要使用动态绑定和替换(substitution);对于这些情况,ACE提供了ACE_Lock和ACE_Lock_Adapter类。使用互斥体类
互斥体实现了“互相排斥”(
mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。任何线程在进入临界区之前,必须获取(
acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。什么时候需要使用互斥体呢?互斥体用于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进行保护,以防止它们在多个线程同时访问时损坏。
下面的例子演示
ACE_Thread_Mutex类的使用。注意在此处很容易用ACE_Mutex替换ACE_Thread_Mutex类,因为它们拥有同样的接口。
例
4-2#include "ace/Synch.h"
#include "ace/Thread.h"
//Arguments that are to be passed to the worker thread are passed
//through this struct.
struct Args
{
public:
Args(int iterations): mutex_(),iterations_(iterations){}
ACE_Thread_Mutex mutex_;
int iterations_;
};
//The starting point for the worker threads
static void* worker(void*arguments)
{
Args *arg= (Args*) arguments;
for(int i=0;i
{
ACE_DEBUG((LM_DEBUG,
"(%t) Trying to get a hold of this iteration\n"));
//This is our critical section
arg->mutex_.acquire();
ACE_DEBUG((LM_DEBUG,"(%t) This is iteration number %d\n",i));
ACE_OS::sleep(2);
//simulate critical work
arg->mutex_.release();
}
return 0;
}
int main(int argc, char*argv[])
{
if(argc<2)
{
ACE_OS::printf("Usage: %s
\n", argv[0]); ACE_OS::exit(1);
}
Args arg(ACE_OS::atoi(argv[2]));
//Setup the arguments
int n_threads = ACE_OS::atoi(argv[1]);
//determine the number of threads to be spawned.
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; }
在上面的例子中,
ACE_Thread包装类用于生成多个线程来执行worker()函数,就如同在前面的例子里一样。Arg对象作为参数传入各个线程,在该对象中含有循环要执行的次数,以及将要使用的互斥体。在此例中,一开始,每个线程立即进入
for循环。一进入循环,线程就进入了临界区。在临界区内完成的工作使用ACE_Thread_Mutex互斥体对象进行保护。该对象由主线程作为参数传给工作者线程。临界区控制是通过在ACE_Thread_Mutex对象上发出acquire()调用,从而获取互斥体的所有权来完成的。一旦互斥体被获取,没有其他线程能够再进入这一代码区。临界区控制是通过使用release()调用来释放的。一旦互斥体的所有权被放弃,就会唤醒所有其他在等待的线程。这些线程随即相互竞争,以获得互斥体的所有权。第一个试图获取所有权的线程会进入临界区。将锁和锁适配器(Lock Adapter)用于动态绑定
如前面所提到的,各种互斥体锁应被直接用于你的代码,或者,如果需要灵活性,作为模板参数来使用。但是,如果你需要动态地(也就是在运行时)改变你的代码所用锁的类型,就无法使用这些锁。
为应对这个问题,
ACE拥有ACE_Lock和ACE_Lock_Adapter类,它们可用于这样的运行时替换(substitution)。下面的例子演示
ACE_Lock类和ACE_Lock_Adapter怎样为应用程序员提供方便,和锁定机制一起使用动态绑定和替换。
例
4-3#include "ace/Synch.h"
#include "ace/Thread.h"
//Arguments that are to be passed to the worker thread are passed
//through this class.
struct Args
{
public:
Args(ACE_Lock* lock,int iterations):
mutex_(lock),iterations_(iterations){}
ACE_Lock* mutex_;
int iterations_;
};
//The starting point for the worker threads
static void* worker(void*arguments)
{
Args *arg= (Args*) arguments;
for(int i=0;i
{
ACE_DEBUG((LM_DEBUG,
"(%t) Trying to get a hold of this iteration\n"));
//This is our critical section
arg->mutex_->acquire();
ACE_DEBUG((LM_DEBUG,"(%t) This is iteration number %d\n",i));
ACE_OS::sleep(2);
//simulate critical work
arg->mutex_->release();
}
return 0;
}
int main(int argc, char*argv[])
{
if(argc<4)
{
ACE_OS::printf("Usage: %s
\n", argv[0]); ACE_OS::exit(1);
}
//Polymorphic lock that will be used by the application
ACE_Lock *lock;
//Decide which lock you want to use at run time,
//recursive or non-recursive.
if(ACE_OS::strcmp(argv[3],"Recursive"))
lock=new ACE_Lock_Adapter
;
else
lock=new ACE_Lock_Adapter
//Setup the arguments
Args arg(lock,ACE_OS::atoi(argv[2]));
//spawn threads and wait as in previous examples..
}
在此例中,和前面的例子唯一的不同是
ACE_Lock类是和ACE_Lock_Adapter一起使用的,以便能提供动态绑定。底层的锁定机制使用递归还是非递归互斥体,是在程序运行时由命令行参数决定的。使用动态绑定的好处是实际的锁定机制可以在运行时被替换。缺点是现在对锁的每次调用都需要负担额外的经由虚函数表的间接层次。使用令牌(Token)
如表
4-1中所提到的,ACE_Token类提供所谓的“递归互斥体”,它可以被最初获得它的同一线程进行多次重新获取。ACE_Token类还确保所有试图获取它的线程按严格的FIFO(先进先出)顺序排序。递归锁允许同一线程多次获取同一个锁。线程不会因为试图获取它已经拥有的锁而死锁。这些类型的锁能在各种不同的情况下派上用场。例如,如果你用一个锁来维护跟踪流的一致性,你可能希望这个锁是递归的,因为某个方法可以调用一个跟踪例程,获取锁,被信号中断,然后再尝试获取这个跟踪锁。如果锁是非递归的,线程将会在这里锁住它自己。你会发现很多其他需要递归锁的有趣应用。重要的是要记住,你获取递归锁多少次,就必须释放它多少次。
在
SunOS 5.x上运行例4-3,释放锁的线程常常也是重新获得它的线程(大约90%的情况是这样)。但是如果你采用ACE_Token类作为锁定机制来运行这个例子,每个线程都会轮流获得令牌,然后有序地把机会让给下一个线程。尽管
ACE_Token作为所谓的递归锁非常有用,它们实际上是更大的“令牌管理”构架的一部分。该构架允许你维护数据存储中数据的一致性。遗憾的是,这已经超出了此教程的范围。
例
4-4#include "ace/Token.h"
#include "ace/Thread.h"
//Arguments that are to be passed to the worker thread are passed
//through this struct.
struct Args
{
public:
Args(int iterations):
token_(“myToken”),iterations_(iterations){}
ACE_Token token_;
int iterations_;
};
//The starting point for the worker threads
static void* worker(void*arguments)
{
Args *arg= (Args*) arguments;
for(int i=0;i
{
ACE_DEBUG((LM_DEBUG,"(%t) Trying to get a hold of this iteration\n"));
//This is our critical section
arg->token_.acquire();
ACE_DEBUG((LM_DEBUG,"(%t) This is iteration number %d\n",i));
//work
ACE_OS::sleep(2);
arg->token_.release();
}
return 0;
}
int main(int argc, char*argv[])
{
//same as previous examples..
}
