c++ 跨平台线程同步对象那些事儿——基于 ace

知道 ACE 这个库的 cpper 绝对要暴露年龄了,在没有 c++11 的年代把模板玩出了花,有的人说这个库适合学习不适合做项目,那今天就来看看学院派的 ACE 是如何封装线程同步对象的,以及当平台不支持时它是如何通过其它对象模拟一个同步对象的前言ACE (Adaptive Communication Environment) 是早年间很火的一个 c++ 开源通讯框架,当时 c++ 的库比较少,以至于谈 c++ 网络通讯就绕不开 ACE,随着后来 boost::asio / libevent / libev … 等专门解决通讯框架的库像雨后春笋一样冒出来,ACE 就渐渐式微了 。特别是它虽然号称是通讯框架,实则把各个平台的基础设施都封装了一个遍,导致想用其中一个部分,也牵一发而动全身的引入了一堆其它的不相关的部分,虽然用起来很爽,但是耦合度太强,学习曲线过于陡峭,以至于坊间流传一种说法:ACE 适合学习,不适合快速上手做项目 。所以后来也就慢慢淡出了人们的视线,不过对于一个真的把它拿来学习的人来说,它的一些设计思想还是不错的,今天就以线程同步对象为例,说一下“史上最全”的 ACE 是怎么封装的,感兴趣的同学可以和标准库、boost 或任意什么跨平台库做个对比,看看它是否当得起这个称呼 。
互斥量互斥量主要就是指各种 mutex 了,依据 mutex 的各种特性,又细分为以下几类:
ACE_Thread_Mutex这个主要是做进程内多线程同步的,底层类型为 ACE_thread_mutex_t,这个类型在不同平台上依赖的设施也不尽相同,可以列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportACE_thread_mutex_tCRITICAL_SECTIONpthread_mutex_tmutex_tSEM_IDintinitInitializeCriticalSectionpthread_mutex_initmutex_initsemMCreaten/aacquireEnterCriticalSectionpthread_mutex_lockmutex_locksemTake (..WAIT_FOREVER..)n/aacquire (..time..)n/apthread_mutex_timedlockn/asemTake (..time..)n/atryacquireTryEnterCriticalSectionpthread_mutex_trylockmutex_trylocksemTake (..NOWAIT..)n/areleaseLeaveCriticalSectionpthread_mutex_unlockmutex_unlocksemGiven/aremoveDeleteCriticalSectionpthread_mutex_destroymutex_destroysemDeleten/a对于上面的表做个简单说明:

  • windows 上就是使用临界区来做线程级别的互斥量;
  • unix like 一般都支持 pthread,例如 AIX / HPUX / IRIX / LYNXOS / MACOSX / UNIXWARE / OPENBSD / FREEBSD ……,如果不支持 pthread,则不在此列;
  • Solaris 有自己的线程库,不使用 pthread;
  • VxWorks 实时操作系统只有一个进程,可以有多个线程 (任务),所以这里使用的是进程级别的同步对象来模拟,具体就是信号灯 (SEM_ID);
  • 对于没有 mutex 支持的系统,使用 int 来定义类别,函数体留空来避免编译报错 (相当于不起作用) 。
另外由于线程同步对象没有对读写做分离,所以 acquire_read / acquire_write / tryacquire_read / tryacquire_write 均使用默认的 acquire / tryacquire 来实现 。带超时参数的 acquire 重载,在有些平台并不被支持,例如 windows 和 Solaris 。
ACE_Recursive_Thread_Mutex与 ACE_Thread_Mutex 相比,增加了已锁定线程再次加锁的能力 (递归进入不死锁) 。底层类型为 ACE_recursive_thread_mutex_t,与它相关的一些设施列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportACE_recursive_thread_mutex_tCRITICAL_SECTIONpthread_mutex_t自定义类型模拟自定义类型模拟intinitInitializeCriticalSectionpthread_mutex_init
(..PTHREAD_MUTEX_RECURSIVE..)
参考自定义类型参考自定义类型n/aacquireEnterCriticalSectionpthread_mutex_lock参考自定义类型参考自定义类型n/aacquire (..time..)n/apthread_mutex_timedlock参考自定义类型参考自定义类型n/atryacquireTryEnterCriticalSectionpthread_mutex_trylock参考自定义类型参考自定义类型n/areleaseLeaveCriticalSectionpthread_mutex_unlock参考自定义类型参考自定义类型n/aremoveDeleteCriticalSectionpthread_mutex_destroy参考自定义类型参考自定义类型n/aget_thread_idn/an/a.owner_id_.owner_id_n/aget_nesting_level.RecursionCount /
.LockCount + 1
n/a.nesting_level_.nesting_level_n/a对于上面的表做个简单说明:
  • windows 的临界区默认就是递归的,所以直接拿来用没有一点儿问题;
  • 支持 pthread 的 unix like 系统,可以为 pthread_mutex_init 设置 PTHREAD_MUTEX_RECURSIVE 参数来指定互斥量是递归的 (当然了,创建 pthread mutex 还有一些其它选项,例如 PTHREAD_MUTEX_ERRORCHECK 可以做一些错误检测并返回错误,而不是直接死锁);
  • Solaris 系统的原生互斥量不支持递归加锁,这里使用自定义类型来模拟,其实只要是不支持递归互斥量的系统,都由这个自定义类型搞定,例如 VxWorks 等;
  • 相对于 ACE_Thread_Mutex,递归版本的增加了两个接口,分别是 get_thread_id 和 get_nesting_level,分别用来获取当前锁的拥有者线程 ID 和嵌套层次,不过貌似只有自定义类型全部支持 。windows 的 CRITICAL_SECTION 可以支持后者,不过对于 32 位系统与 64 位系统有略微差别,前者使用 CRITICAL_SECTION 的 RecursionCount 字段,后者使用 LockCount 字段;
  • 对于没有 mutex 支持的系统,使用 int 来定义类别,函数体留空来避免编译报错 (相当于不起作用) 。
带超时参数的 acquire 重载,在有些平台并不被支持,例如 windows 。自定义类型的通用定义如下:
1 class ACE_recursive_thread_mutex_t 2 { 3 public: 4/// Guards the state of the nesting level and thread id. 5ACE_thread_mutex_t nesting_mutex_; 67/// This condition variable suspends other waiting threads until the 8/// mutex is available. 9ACE_cond_t lock_available_;10 11/// Current nesting level of the recursion.12int nesting_level_;13 14/// Current owner of the lock.15ACE_thread_t owner_id_;16 };而其中具体使用的平台设施,又随 ACE_thread_mutex_t / ACE_cond_t / ACE_thread_t 的定义而不同 。关于如何基于非递归 mutex 与 condition variable 来实现递归 mutex,这个留在后面详细说明 。
ACE_RW_Thread_Mutex与  ACE_Thread_Mutex 相比,ACE_RW_Thread_Mutex 允许对读和写分别加锁,以提高读的并行程度 (读-写、写-写之间还是互斥的,读-读可以同时进入) 。底层类型为 ACE_rwlock_t,与它相关的一些设施列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportACE_rwlock_t自定义类型模拟pthread_rwlock_trwlock_t自定义类型模拟intinit参考自定义类型pthread_rwlock_initrwlock_init参考自定义类型n/aacquire_read参考自定义类型pthread_rwlock_rdlockrw_rdlock参考自定义类型n/atryacquire_read参考自定义类型pthread_rwlock_tryrdlockrw_tryrdlock参考自定义类型n/aacquire_write参考自定义类型pthread_rwlock_wrlockrw_wrlock参考自定义类型n/atryacquire_write参考自定义类型pthread_rwlock_trywrlockrw_trywrlock参考自定义类型n/atryacquire_write_upgrade参考自定义类型n/an/a参考自定义类型n/arelease参考自定义类型pthread_rwlock_unlockrw_unlock参考自定义类型n/aremove参考自定义类型pthread_rwlock_destroyrwlock_destroy参考自定义类型n/a对于上面的表做个简单说明:
  • 支持 pthread 的 unix like 系统,可以直接基于 pthread_rwlock_t 原生类型进行封装;
  • Solaris 系统的原生读写锁 rwlock_t 本身就可以支持上述接口;
  • windows 没有读写锁原生支持,这里使用自定义类型来模拟,其实只要是不支持读写锁的系统,都由这个自定义类型搞定,例如 VxWorks ;
  • 读写锁的 acquire 分为 acquire_read / acquire_write 分别表示获取读锁与写锁;同理,tryacquire 也分为 tryacquire_read / tryacquire_write;而通用的 acquire 其实就是 acquire_write,tryacquire 就是 tryacquire_write;没有列出带超时参数的 acquire 重载,因为底层都不支持;另外读写锁还增加了一个 tryacquire_write_upgrade 接口,用来给已经获取读锁的线程升级为写锁,不过目前仅有模拟的自定义类型支持该接口;
  • 对于没有 mutex 支持的系统,使用 int 来定义类别,函数体留空来避免编译报错 (相当于不起作用)
该自定义类型的通用定义如下:
1 struct ACE_Export ACE_rwlock_t 2 { 3 public: 4 //protected: 56ACE_mutex_t lock_; 7// Serialize access to internal state. 89ACE_cond_t waiting_readers_;10// Reader threads waiting to acquire the lock.11 12int num_waiting_readers_;13// Number of waiting readers.14 15ACE_cond_t waiting_writers_;16// Writer threads waiting to acquire the lock.17 18int num_waiting_writers_;19// Number of waiting writers.20 21int ref_count_;22// Value is -1 if writer has the lock, else this keeps track of the23// number of readers holding the lock.24 25int important_writer_;26// indicate that a reader is trying to upgrade27 28ACE_cond_t waiting_important_writer_;29// condition for the upgrading reader30 };而其中具体使用的平台设施,又随 ACE_mutex_t / ACE_cond_t 的定义而不同 。关于如何使用 mutex 与 condition variable 来实现读写锁,这个留在后面详细说明 。
ACE_Process_Mutex这个主要是做进程间线程同步的,底层类型为 ACE_Mutex 或 ACE_SV_Semaphore_Complex,前者是通用的进程间互斥量,后者依赖 System V IPC 机制,默认使用前者,如果所在平台支持,可以通过定义宏 ACE_USES_MUTEX_FOR_PROCESS_MUTEX 来切换到后者,但是我看系统预定义的各平台头文件,都没有定义这个宏,所以还是重点看一下前者的实现 。ACE_Mutex 底层类型为 ACE_mutex_t,这个类型在不同平台上依赖的设施也不尽相同,可以列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportACE_mutex_tHANDLEpthread_mutex_tmutex_tSEM_IDintinitCreateMutexpthread_mutex_init (..PTHREAD_PROCESS_SHARED..)mutex_initsemMCreaten/aacquireWaitForSingleObject (..INFINITE..)pthread_mutex_lockmutex_locksemTake (..WAIT_FOREVER..)n/aacquire (..time..)WaitForSingleObject (..time..)pthread_mutex_timedlockn/asemTake (..time..)n/atryacquireWaitForSingleObject (..0..)pthread_mutex_trylockmutex_trylocksemTake(..NOWAIT..)n/areleaseReleaseMutexpthread_mutex_unlockmutex_unlocksemGiven/aremoveCloseHandlepthread_mutex_destroymutex_destroysemDeleten/a 对于上面的表做个简单说明:
  • windows 上就是使用互斥量来做进程级别的互斥;
  • 支持 pthread 的 unix like 系统,可以直接基于 pthread_mutex_t 原生类型进行封装,不过相比进程内互斥量,需要多做两个工作:
    • 创建或打开一块共享内存,在该内存上创建互斥量,需要使用该互斥量的进程,都打开这块共享内存进行操作;
    • 创建互斥量时指定 PTHREAD_PROCESS_SHARED 属性 。
  • Solaris 自己的 mutex_t 就可以支持进程间的互斥,在 type 中指定 USYNC_PROCESS 标志位即可 (进程内的指定 USYNC_THREAD);
  • VxWorks 实时操作系统只有一个进程,所以无所谓进程间互斥量了,因此还是使用信号灯 SEM_ID 来模拟;
  • 对于没有 mutex 支持的系统,使用 int 来定义类别,函数体留空来避免编译报错 (相当于不起作用) 。
另外由于线程同步对象没有对读写做分离,所以 acquire_read / acquire_write / tryacquire_read / tryacquire_write 均使用默认的 acquire / tryacquire 来实现 。ACE_mutex_t 和 ACE_thread_mutex_t 的一个最大不同是,前者可以根据传入的 type 自动决定是使用进程内还是进程间的互斥量,例如在 windows 上,它的类型其实是一个 union:
1 typedef struct 2 { 3/// Either USYNC_THREAD or USYNC_PROCESS 4int type_; 5union 6{ 7HANDLE proc_mutex_; 8CRITICAL_SECTION thr_mutex_; 9};10 } ACE_mutex_t;
使用 HANDLE 还是 CRITICAL_SECTION,完全由 type 决定,当然,在 ACE_Process_Mutex 中,是明确指定了 type 为 USYNC_PROCESS 的 。
ACE_RW_Process_Mutex与  ACE_RW_Thread_Mutex 相比,它提供了进程间多线程读写锁的能力 。基于 ACE_File_Lock,而它的底层类型是 ace_flock_t,其实也是一个自定义类型,主要封装了不同平台上的文件锁,因此 ACE_RW_Thread_Mutex 其实只能做进程间的读写锁,而不能做进程内线程间的读写锁,这是因为一般的文件锁的粒度是到进程而不是线程的 (进程内多个线程去获取锁,都会得到锁已获取的结果,完全没有锁的效果) 。与 ace_flock_t 相关的一些设施列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportace_flock_tHADNLE/OVERLAPPEDint/struct flockint/struct flockint/struct flockintinitCreateFileopenopenn/an/aacquire_readLockFile[Ex]fcntl (..F_RDLCK..F_SETLKW..)fcntl (..F_RDLCK..F_SETLKW..)n/an/atryacquire_readLockFileEx (..LOCKFILE_FAIL_IMMEDIATELY..)fcntl (..F_RDLCK..F_SETLK..)fcntl (..F_RDLCK..F_SETLK..)n/an/aacquire_writeLockFileEx (..LOCKFILE_EXCLUSIVE_LOCK..)fcntl (..F_WRLCK..F_SETLKW..)fcntl (..F_WRLCK..F_SETLKW..)n/an/atryacquire_writeLockFileEx (..LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK..)
fcntl (..F_WRLCK..F_SETLK..)fcntl (..F_WRLCK..F_SETLK..)n/an/atryacquire_write_upgradeLockFileEx (..LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK..)fcntl (..F_WRLCK..F_SETLK..)fcntl (..F_WRLCK..F_SETLK..)n/an/areleaseUnlockFilefcntl (..F_UNLCK..F_SETLK..)fcntl (..F_UNLCK..F_SETLK..)n/an/aremoveCloseHandle/DeleteFileclose/unlinkclose/unlinkn/an/a对于上面的表做个简单说明:
  • windows 系统基于原生的文件锁进行封装;
  • unix like 系统 (包含 Solaris) 可以直接基于 struct flock 原生类型进行封装;
  • 除了上面列出的接口,还有通用的 acquire 和 tryacquire,它们其实就是通过 acquire_write 和 tryacquire_write 来实现的;带超时参数的 acquire 重载没有列出,因为底层都不支持;另外 tryacquire_write_upgrade 接口底层是通过 tryacquire_write 来实现的,底层文件锁具备直接将读锁转化为写锁的接口;
  • 所有接口基本上都使用 start / whence / len 来指定锁定或解锁的文件范围,这个与其它锁参数还是有很大不同的,好在如果只是锁定文件第一个字节,ace 提供的默认值就够了,所以还能有一定通用性的 (可以在某些模板中通过不带参数的方式来调用);
  • 对于没有文件锁支持的系统,使用 int 来定义类别 (VxWorks 虽然定义了 flock 但是没有相应的机制来实现文件锁功能),函数体留空来避免编译报错 (相当于不起作用) 。
下面是 ace_flock_t 的具体定义:
1/** 2* @class ace_flock_t 3* 4* @brief OS file locking structure. 5*/ 6class ACE_Export ace_flock_t 7{ 8public: 9/// Dump state of the object.10void dump (void) const;11 12 # if defined (ACE_WIN32)13ACE_OVERLAPPED overlapped_;14 # else15struct flock lock_;16 # endif /* ACE_WIN32 */17 18/// Name of this filelock.19const ACE_TCHAR *lockname_;20 21/// Handle to the underlying file.22ACE_HANDLE handle_;23};可以看到就是主要支持两类:windows 的重叠 IO 和支持文件锁的 unix like 系统 。
ACE_RW_Mutex通用的读写锁类型,ACE_RW_Thread_Mutex 基类,与后者不同的是,它提供了 type 类型来指定共享的范围是进程内 (USYNC_THREAD) 还是进程间 (USYNC_PROCESS),ACE_RW_Thread_Mutex 就是通过传递 USYNC_THREAD 来实现的 。底层类型同为 ACE_rwlock_t,这里重点考察一下它在 posix 与 solaris 上底层设施的差别:
平台/接口/设施unix like (pthread)SolarisACE_rwlock_tpthread_rwlock_trwlock_tinitpthread_rwlock_initrwlock_initacquire_readpthread_rwlock_rdlockrw_rdlocktryacquire_readpthread_rwlock_tryrdlockrw_tryrdlockacquire_writepthread_rwlock_wrlockrw_wrlocktryacquire_writepthread_rwlock_trywrlockrw_trywrlocktryacquire_write_upgraden/an/areleasepthread_rwlock_unlockrw_unlockremovepthread_rwlock_destroyrwlock_destroy其中 rwlock_init 接收一个 type 参数用于表示进程内、进程间共享 (USYNC_THREAD | USYNC_PROCESS);pthread_rwlock_init 也是如此,不过具体类型定义与 Solaris 上有所不同 (THREAD_PROCESS_SHARED | THREAD_PROCESS_PRIVATE),ACE 内部会做适当转换 。这两组接口都不支持原生 name (虽然接口出于一致性提供了,但是内部都没有使用),是通过将读写锁放在共享内存中实现跨进程访问的,这一点需要特别注意 。
条件变量条件变量主要源自于 pthread 中的 condition variable,依据条件变量配合使用的 mutex 的不同,又细分为以下几类:
ACE_Condition_Thread_Mutex这个主要是做进程内多线程等待与通知的,底层类型为 ACE_cond_t 与 ACE_Thread_Mutex,后者上面已经说明过了,下面重点说一下前者,它在不同平台上依赖的设施也不尽相同,可以列表如下:
平台/接口/设施windowsunix like (pthread)SolarisVxWorksunsupportACE_cond_t自定义类型模拟pthread_cond_tcond_t自定义类型模拟intinit参考自定义类型pthread_cond_initcond_init参考自定义类型n/await参考自定义类型pthread_cond_waitcond_wait参考自定义类型n/await(..timeout..)参考自定义类型pthread_cond_timedwaitcond_timedwait参考自定义类型n/asignal参考自定义类型pthread_cond_signalcond_signal参考自定义类型n/abroadcast参考自定义类型pthread_cond_broadcastcond_broadcast参考自定义类型n/aremove参考自定义类型pthread_cond_destroycond_destroy参考自定义类型n/a对于上面的表做个简单说明:
  • 支持 pthread 的 unix like 系统,可以直接基于 pthread_cond_t 原生类型进行封装;
  • Solaris 系统的原生条件变量 cond_t 本身就可以支持上述接口;
  • windows 没有原生条件变量支持,这里使用自定义类型来模拟,其实只要是不支持条件变量的系统,都由这个自定义类型搞定,例如 VxWorks  等;
  • 条件变量的 wait 有两个重载,第二个可以带超时参数,此时对应的底层设施和第一个接口是不一样的;signal 用于唤醒一个线程;broadcast 用于唤醒所有等待在这个条件变量上的线程,不过最终仍只有一个线程可获取锁从而进入条件变量中;
  • 对于没有 thread mutex 和信号灯或事件支持的系统 (模拟类型所依赖的基础设施),使用 int 来定义 ACE_cond_t 类型、函数体留空,来避免编译报错 (相当于不起作用) 。
该自定义类型的通用定义如下:
1 class ACE_Export ACE_cond_t 2 { 3 public: 45/// Returns the number of waiters. 6long waiters (void) const; 78 //protected: 9/// Number of waiting threads.10long waiters_;11 12/// Serialize access to the waiters count.13ACE_thread_mutex_t waiters_lock_;14 15/// Queue up threads waiting for the condition to become signaled.16ACE_sema_t sema_;17 18 #if defined (VXWORKS)19/**20* A semaphore used by the broadcast/signal thread to wait for all21* the waiting thread(s) to wake up and be released from the22* semaphore.23*/24ACE_sema_t waiters_done_;25 #elif defined (ACE_WIN32)26/**27* An auto reset event used by the broadcast/signal thread to wait28* for the waiting thread(s) to wake up and get a chance at the29* semaphore.30*/31HANDLE waiters_done_;32 #else33 #error "Please implement this feature or check your config.h file!"34 #endif /* VXWORKS || ACE_PSOS */35 36/// Keeps track of whether we were broadcasting or just signaling.37size_t was_broadcast_;38 };而其中具体使用的平台设施,又随 ACE_thread_mutex_t / ACE_sema_t / event 的定义而不同 (waiters_done_ 成员还特别区分了 VxWorks 与 Win32 平台,前者基于信号灯,后者基于事件) 。关于如何使用 mutex 与 semaphore 或 event 来实现条件变量,这个留在后面详细说明 。
ACE_Condition <MUTEX>通用类型的条件变量,底层的互斥量可通过模板参数传递 。与 ACE_Thread_Mutex_Condition 唯一的不同之处是提供了 type 类型来指定共享的范围是进程内 (USYNC_THREAD) 还是进程间 (USYNC_PROCESS),底层类型同为 ACE_cond_t,这里重点考察一下它在 posix 与 solaris 上的底层设施:
平台/接口/设施unix like (pthread)SolarisACE_cond_tpthread_cond_tcond_tinitpthread_cond_initcond_initwaitpthread_cond_waitcond_waitwait(..timeout..)pthread_cond_timedwaitcond_timedwaitsignalpthread_cond_signalcond_signalbroadcastpthread_cond_broadcastcond_broadcastremovepthread_cond_destroycond_destroy其中 cond_init 接收一个 type 参数用于表示进程内、进程间共享 (USYNC_THREAD | USYNC_PROCESS);pthread_cond_init 也是如此,不过具体类型定义与 Solaris 上有所不同 (THREAD_PROCESS_SHARED | THREAD_PROCESS_PRIVATE),ACE 内部会做适当转换 。这两组接口都不支持原生 name (虽然接口出于一致性提供了,但是内部都没有使用),是通过将条件变量放在共享内存中实现跨进程访问的,这一点需要注意 。
用于 ACE_Condition 的 MUTEX 模板参数,只能是下面几类:
  • ACE_Thread_Mutex
  • ACE_Recursive_Thread_Mutex
  • ACE_Null_Mutex
前两个已经在前面介绍过了,ACE_Null_Mutex 请参考后面 NULL 那一章 。
ACE_Thread_Condition <MUTEX>进程内多线程等待与唤醒的通用的条件变量,派生自 ACE_Condition <MUTEX>,并指定了使用 USYNC_THREAD 类型 。它与 ACE_Condition_Thread_Mutex 作用完全一致,其实 ACE 作者的本意是定义它的实例化来作为 ACE_Condition_Thread_Mutex:
typedef ACE_Condition_Thread_Mutex ACE_Thread_Condition <ACE_Thread_Mutex>; 不过由于某些古老编译器的限制,这一实例化受限,于是不得不重新定义了一个 ACE_Condition_Thread_Mutex 。不过这个类型也有好处,就是可以指定 MUTEX 类型,目前支持的类型与其父类 ACE_Condition <MUTEX> 相同 。
ACE_Condition_Recursive_Thread_Mutex与  ACE_Condition_Thread_Mutex 相比,增加了同 ACE_Recursive_Thread_Mutex 配合使用的能力 。底层类型为 ACE_cond_t 与 ACE_Recursive_Thread_Mutex,涉及 ACE_cond_t 类型的底层设施上面已经说明过了,这里没有改变 。其实 ACE_Condition_Recursive_Thread_Mutex 是 ACE_Condition <MUTEX> 模板使用 ACE_Recursive_Thread_Mutex 作为 MUTEX 模板参数的一个特化,后者与 ACE_Condition_Thread_Mutex 的关系前面已经介绍过了,可以认为是等价的 。
而新的特化专门为等待递归锁 (wait 的两个重载) 提供了一份新的实现,用于在等待条件时释放 nesting_level 级别个锁、并在条件满足被唤醒后重新获取 nesting_level 个锁,从而保证在等待期间其它线程可以进入锁,避免死锁的发生 。其实条件变量一般为了避免这种多层加锁导致的死锁问题,很少和递归锁配合使用,一般是和非递归锁一起用,所以非不得已,一般不使用这个类型 。
上面的类型可能有点让人眼晕,画个图说明一下它们之间的关系:
c++ 跨平台线程同步对象那些事儿——基于 ace

文章插图
ACE 因为兼容大量老旧平台与编译器,不得不在某些场景舍弃他们最爱的模板,不然的话代码还可以更为精简 。
信号灯信号灯就是 semaphore 了,它提供经典的 PV 操作,是操作系统同步的基石之一,所以很多平台都会支持 。依据 semaphore 的各种特性,又细分为以下几类:
ACE_Thread_Semaphore这个主要是做进程内同步的,底层类型为 ACE_sema_t,这个类型在不同平台上依赖的设施也不尽相同,可以列表如下:
平台/接口/设施windowswinceunix like (posix)unix like (sysv)SolarisVxWorksunsupportACE_sema_tHANDLE自定义类型 Isem_t自定义类型 IIsema_tSEM_IDintinitCreateSemaphore参考自定义类型 Isem_init / sem_open参考自定义类型 IIsema_initsemCCreaten/aacquireWaitForSingleObject (..INFINITE..)参考自定义类型 Isem_wait参考自定义类型 IIsema_waitsemTake (..WAIT_FOREVER..)n/aacquire (..time..)WaitForSingleObject (..time..)参考自定义类型 In/a参考自定义类型 IIn/asemTake (..time..)n/atryacquireWaitForSingleObject (..0..)参考自定义类型 Isem_trywait参考自定义类型 IIsema_trywaitsemTake (..NOWAIT..)n/areleaseReleaseSemaphore (..1..)参考自定义类型 Isem_post参考自定义类型 IIsema_postsemGiven/arelease (..N..)ReleaseSemaphore (..N..)循环调用 release N 次循环调用 release N 次循环调用 release N 次循环调用 release N 次循环调用 release N 次n/aremoveCloseHandle参考自定义类型 Isem_unlink / sem_close / sem_destroy参考自定义类型 IIsema_destroysemDeleten/a对于上面的表做个简单说明:
  • windows 上就是使用原生 Semaphore 来做信号灯;
  • wince  (Windows CE) 某些版本之前不支持原生 Semaphore,这里使用事件 (event) 和临界区 (CRITICAL_SECTION) 来模拟,定义为类型 I;
  • unix like 一般都支持 posix 标准,可以直接使用 posix 定义的 sem_t 类型来实现信号灯,它既支持匿名信号灯 (sem_init / sem_destroy)、也支持命名信号灯 (sem_open / sem_unlink / sem_close),根据用户需求 (是否传递有效的 name 参数) 来决定使用的底层接口 。奇怪的是 posix semaphore 有 sem_timedwait 接口,而 ACE 却没有封装,不知道是不是我使用的版本太老的缘故;
  • 一些早期的平台对 posix 标准支持不全,它们没有 posix semaphore 可用,这里基于互斥量 (pthread_mutex_t) 和条件变量 (pthread_cond_t) 来模拟,定义为类型 II;
  • Solaris 有自己原生的 sema_t,不使用 posix 信号灯,注意它和 posix 上的 sem_t 不是一个类型,sem 与  sema 一字之差,但是完全是两套接口,Solaris 上不支持命名信号灯 。不过 Solaris 后续版本也支持 posix 信号灯,所以具体使用哪个,要看系统版本而定;
  • VxWorks 有自己原生的 SEM_ID 来做信号灯;
  • 对于没有 semaphore 支持的系统,使用 int 来定义类别,函数体留空来避免编译报错 (相当于不起作用) 。
另外由于线程同步对象没有对读写做分离,所以 acquire_read / acquire_write / tryacquire_read / tryacquire_write 均使用默认的 acquire / tryacquire 来实现;对于 release 接口,提供一个一次释放 N 次信号灯的重载,对于该重载,除 wince 平台是调整接口 (ReleaseSemaphore) 参数外,其它的都是通过循环调用释放接口来模拟的 。
自定义类型 I 定义如下:
1 /** 2* @class ACE_sema_t 3* 4* @brief Semaphore simulation for Windows CE. 5*/ 6 class ACE_Export ACE_sema_t 7 { 8 public: 9/// Serializes access to <count_>.10ACE_thread_mutex_t lock_;11 12/// This event is signaled whenever the count becomes non-zero.13ACE_event_t count_nonzero_;14 15/// Current count of the semaphore.16u_int count_;17 };因为限定了 wince 平台,所以 ACE_thread_mutex_t 就是 CRITICAL_SECTION,ACE_event_t 就是 HANDLE (Event) 了 。自定义类型 II 定义如下:
1 /** 2* @class ACE_sema_t 3* 4* @brief This is used to implement semaphores for platforms that support 5* POSIX pthreads, but do *not* support POSIX semaphores, i.e., 6* it's a different type than the POSIX <sem_t>. 7*/ 8 class ACE_Export ACE_sema_t 9 {10 public:11/// Serialize access to internal state.12ACE_mutex_t lock_;13 14/// Block until there are no waiters.15ACE_cond_t count_nonzero_;16 17/// Count of the semaphore.18u_long count_;19 20/// Number of threads that have called <ACE_OS::sema_wait>.21u_long waiters_;22 };
因为限定了 unix 平台,所以 ACE_mutex_t 就是 pthread_mutex_t,ACE_cond_t 就是 pthread_cond_t 了 。关于如何基于它们来实现信号灯,这个留在后面再说 。
此外,为了保存命名信号灯的名称,支持 posix semaphore 的平台和 VxWorks 并不是直接使用 sem_t 和 SEM_ID 的,而是将它们和 name 组合成一个结构体一起来使用:
1 typedef struct 2 { 3/// Pointer to semaphore handle.This is allocated by ACE if we are 4/// working with an unnamed POSIX semaphore or by the OS if we are 5/// working with a named POSIX semaphore. 6sem_t *sema_; 78/// Name of the semaphore (if this is non-NULL then this is a named 9/// POSIX semaphore, else its an unnamed POSIX semaphore).10char *name_;11 12 #if defined (ACE_LACKS_NAMED_POSIX_SEM)13/// this->sema_ doesn't always get created dynamically if a platform14/// doesn't support named posix semaphores.We use this flag to15/// remember if we need to delete <sema_> or not.16int new_sema_;17 #endif /* ACE_LACKS_NAMED_POSIX_SEM */18 } ACE_sema_t;不过好像因为 VxWorks 本身不支持命名信号灯,所以它这个成员一直保持为 NULL:
1 // Use VxWorks semaphores, wrapped ...2 typedef struct3 {4/// Semaphore handle.This is allocated by VxWorks.5SEM_ID sema_;6 7/// Name of the semaphore:always NULL with VxWorks.8char *name_;9 } ACE_sema_t;ACE_Process_Semaphore这个主要是做进程间线程同步的,底层类型视不同平台分别为 ACE_Semaphore 或 ACE_SV_Semaphore_Complex,在 Windows 与支持 posix semaphore 的平台上使用前者,因为它们原生的信号灯本身就支持跨进程使用;对于支持 SystemV semaphore 的平台则使用后者,它封装了 sysv 相关的信号灯 。ACE_Semaphore 其实就是 ACE_Thread_Semaphore 的基类,因而它的一些封装和上一节完全一样,不同的地方主要在于 posix semaphore 的跨进程处理上:
  • 如果该信号灯是有名的,则使用 sem_open / sem_close / sem_unlink 接口来操作命名信号灯,不同进程可以通过名称来指定一个唯一的全局信号灯;
  • 如果该信号灯是匿名的,则使用 sem_init / sem_destroy  在共享内存上创建对应的信号灯,不同的进程都映射这个共享内存来操作匿名的信号灯 。
对于 sysv 信号灯,则使用另外一套完全不同的接口,该接口早于 posix 信号灯存在,因而也被许多系统广泛的支持,具体依赖的接口列表如下:
平台/接口/设施windowsunix like (posix)unix like (sysv)SolarisVxWorksunsupportACE_sema_tHANDLEsem_tintsema_tSEM_IDintinitCreateSemaphoresem_init / sem_opensemget / semctl (..SETVAL..)sema_initsemCCreaten/aacquireWaitForSingleObject (..INFINITE..)sem_waitsemop (..-1..)sema_waitsemTake (..WAIT_FOREVER..)n/atryacquireWaitForSingleObject (..0..)sem_trywaitsemop (..-1..IPC_NOWAIT..)sema_trywaitsemTake (..NOWAIT..)n/areleaseReleaseSemaphore (..1..)sem_postsemop (..1..)sema_postsemGiven/aremoveCloseHandlesem_unlink / sem_close / sem_destroysemctl (..IPC_RMID..)sema_destroysemDeleten/a