本文使用 C++ RAII 机制来封装互斥量、条件变量,使其自动管理互斥量、条件变量的生命周期,避免手动维护带来的资源泄露等各类问题。本文使用的是 Linux 下 Pthread 库。c++
首先封装 mutex,下面为实现:segmentfault
class MutexLock : noncopyable { public: MutexLock() { assert(pthread_mutex_init(&mutex_, nullptr) == 0); } ~MutexLock() { assert(pthread_mutex_destroy(&mutex_) == 0); } /** * 加锁(仅供 MutexLockGuard 调用,严禁用户调用) */ void lock() { pthread_mutex_lock(&mutex_); } /** * 解锁(仅供 MutexLockGuard 调用,严禁用户调用) */ void unlock() { pthread_mutex_unlock(&mutex_); } /** * 获取互斥量原始指针(仅供 Condition 调用,严禁用户调用) * 仅供 Condition 调用 * @return 互斥量原始指针 */ pthread_mutex_t* getPthreadMutexPtr() { return &mutex_; } private: pthread_mutex_t mutex_{}; // 互斥量 };
如今MutexLock
对象已经能够自动管理 mutex 了,它在构造函数中初始化 mutex,在析构函数中销毁 mutex。多线程
MutexLock
对象有一点不足的是,通常加锁和解锁是成对出现的,可是使用 MutexLock
对象的时候,须要本身手动进行加锁和解锁。这个问题比较容易解决,引入一个 MutexLockGuard
对象来管理加锁和解锁便可。实现以下:函数
class MutexLockGuard : noncopyable { public: explicit MutexLockGuard(MutexLock &mutex) : mutex_(mutex) { mutex_.lock(); } ~MutexLockGuard() { mutex_.unlock(); } private: MutexLock &mutex_; };
MutexLockGuard
对象使用也比较简单。线程
{ // mutex 为 MutexLock 对象 MutexLockGuard lock(mutex); ... }
当 MutexLockGuard
对象建立的时候,加锁。当 MutexLockGuard
对象离开做用域时,MutexLockGuard
对象执行析构函数,解锁。有了 MutexLockGuard
对象,就不须要手动进行加锁和解锁了。指针
MutexLockGuard
对象还有一个小问题:MutexLockGuard
的临时对象能不能达到咱们想要的自动加锁和解锁的效果呢?code
{ // mutex 为 MutexLock 对象 MutexLockGuard(mutex); // MutexLockGuard 对象已被销毁 ... }
答案是能自动加锁和解锁,但不能保护临界区。临时对象在建立后会当即被销毁,并非在离开做用域的时候被销毁,因此临时对象必不能达到锁住资源的效果。咱们能够定义一个宏来阻止临时对象的使用。对象
#define MutexLockGuard(x) error "Missing guard object name"
这样就完成了对互斥量的封装了。继承
有了前面的互斥量的封装,分装条件变量就简单多了。直接看代码:生命周期
class Condition : noncopyable { public: /** * 构造函数 * @param mutex 互斥量 */ explicit Condition(MutexLock &mutex) : mutex_(mutex) { assert(pthread_cond_init(&cond_, nullptr) == 0); } ~Condition() { assert(pthread_cond_destroy(&cond_) == 0); } void wait() { pthread_cond_wait(&cond_, mutex_.getPthreadMutexPtr()); } /** * 等待规定时间 * @param second 等待的时间 * @return 若是超时,则返回true;不然,返回false */ bool waitForSecond(int second) { struct timespec timeout{}; clock_getres(CLOCK_REALTIME, &timeout); timeout.tv_sec += second; return pthread_cond_timedwait(&cond_, mutex_.getPthreadMutexPtr(), &timeout) == ETIMEDOUT; } void notify() { pthread_cond_signal(&cond_); } void notifyAll() { pthread_cond_broadcast(&cond_); } private: MutexLock &mutex_; pthread_cond_t cond_{}; };
由于条件变量要配合互斥量使用,须要获取互斥量的指针,因此 MutexLock
对象提供了获取互斥量指针的 getPthreadMutexPtr
成员函数。 C++ RAII机制不提倡传递裸指针,由于这样作会有很大的风险。正如《Effective C++》第三版条款 15 中说到,这个世界并不完美,不少 APIs 直接涉及资源,例如 Unix 系统 API。因此说,这是无奈之举。
若是你留意的话,应该注意到前面的三个对象都继承了 noncopyable
对象,由于他们都是对象语义的,是不可拷贝的。可参考C++ noncopyable类。该实现只是简单的实现,还有不少问题没有考虑(例如,未考虑多线程的状况),彻底没有达到工业强度。