共计 2411 个字符,预计需要花费 7 分钟才能阅读完成。
本文使用 C++ RAII 机制来封装互斥量、条件变量,使其自动管理互斥量、条件变量的生命周期,避免手动维护带来的资源泄露等各种问题。本文使用的是 Linux 下 Pthread 库。
互斥量
MutexLock
首先封装 mutex,下面为实现:
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。
MutexLockGuard
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
的临时对象能不能达到我们想要的自动加锁和解锁的效果呢?
{
// 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 类。
该实现只是简单的实现,还有很多问题没有考虑(例如,未考虑多线程的情况),例如完全没有达到工业强度。