文章目录
互斥量
互斥量保护临界区,任何时候最多只有一个线程在同一个互斥量保护的临界区内活动
使用互斥量的原则
- 用RAII手法封装互斥量,即使用MutexLockGuard,不自己调用互斥量的lock()和unlock()
- 只使用non-recursive互斥量:recursive mutex(递归的互斥量/可重入的互斥量)和non-recursive mutex(非递归的互斥量/不可重入的互斥量)的区别:同一个线程可以重复对一个recursive mutex加锁,但是如果重复对一个non-recursive mutex加锁会导致死锁。
- 每次构造MutexLockGuard对象的时候,思考调用栈上已经持有的锁,防止加锁顺序不同导致死锁。
- 死锁的调试方法:把线程调用栈打印出来分析;用PTHREAD_MUTEX_ERRORCHECK来排错。
- 尽量不要用读写锁和信号量
RAII
MutexLock
class MutexLock : boost::noncopyable {
public:
MutexLock():holder_(0) {
pthread_mutex_init(&mutex_, NULL);
}
~MutexLock() {
assert(holder_ == 0);
pthread_mutex_destroy(&mutex_);
}
void lock() { //仅供MutexLockGuard调用
pthread_mutex_lock(&mutex_);
holder_ = CurrentThread::tid();
}
void unlock() { //仅供MutexLockGuard调用
holder_ = 0;
pthread_mutex_unlock(&mutex_);
}
bool isLockedByThisThread() {
return holder_ == CurrentThread::tid();
}
void assertLocked() {
assert(isLockedByThisThread());
}
pthread_mutex_t* getPthreadMutex() { //仅供Condition调用
return &mutex_;
}
private:
pthread_mutex_t mutex_;
pid_t holder_;
};
MutexLockGuard
#include "MutexLock.h"
class MutexLockGuard {
public:
explicit MutexLockGuard(MutexLock& mutex):mutex_(mutex){
mutex_.lock();
}
~MutexLockGuard() {
mutex_.unlock();
}
private:
MutexLock& mutex_;
};
#define MutexLockGuard(x) static_assert(false, "missing mutex guard var name");
//编译期断定,防止出现漏写变量名,生成临时变量马上被销毁,并没有上锁的情况
使用non-recursive mutex
一个使用recursive mutex存在问题的例子
MutexLock mutex;
vector<Foo> vec;
void post(const Foo& foo) {
MutexLockGuard guard(mutex);
vec.push_back(foo);
}
void traverse() {
MutexLockGuard guard(mutex);
for (auto itr = vec.begin(); itr != vec.end(); ++itr) {
itr->doit(); //如果doit()调用了post(),可能导致vector扩容,进而迭代器失效,出错
}
}
解决方法1
提供两种类型的post()函数,一种加锁,一种不加锁,并且用isLockedByThisThread()函数检查是否持有锁。
void post(const Foo& foo){
MutexLockGuard guard(mutex);
vec.push_back(foo);
}
void postWithLockHold(const Foo& foo){
assert(mutex.isLockedByThisThread());
vec.push_back(foo);
}
解决方法2
推迟修改:traverse()函数遍历数组的时候把需要修改的元素位置记下来,遍历完了再单独修改
解决方法3
copy-on-wtire:用shared_ptr模拟读写锁的功能
- 对于write端,当引用计数为1时可以放心地写,当引用计数大于1时怎么处理?
- 对于read端,读之前把引用计数加1,读完之后减1。
class Foo {
public:
void doit();
};
typedef vector<Foo> FooList;
typedef shared_ptr<FooList> FooListPtr;
MutexLock mutex;
FooListPtr g_foos;
void traverse() {
FooListPtr foos;
{
MutexLockGuard guard(mutex); //临界区只保护对g_foos的读
foos = g_foos;
assert(!g_foos.unique());
}
for (auto itr = foos->begin(); itr != foos->end(); ++itr) {
itr->doit();
}
}
void post(const Foo& f) {
MutexLockGuard guard(mutex); //加锁范围为整个复制和修改
if (!g_foos.unique()) {
g_foos.reset(new FooList(*g_foos));
}
assert(g_foos.unique());
g_foos->push_back(f);
}
void Foo::doit() {
Foo f;
post(f);
}
int main() {
g_foos.reset(new FooList);
Foo f;
post(f);
traverse();
}
条件变量
条件变量学名为管程(monitor),用于线程被等待唤醒和唤醒一个或多个线程,通知条件变化或者资源可用。
- 条件变量的正确用法:以线程安全的BlockingQueue和CountDownLatch为例
条件变量的封装Condition
#include "MutexLock.h"
class Condition {
public:
Condition(MutexLock& mu):mutex_(mu){
pthread_cond_init(&cond_, NULL);
}
~Condition() {
pthread_cond_destroy(&cond_);
}
void notify() {
pthread_cond_signal(&cond_);
}
void notifyAll() {
pthread_cond_broadcast(&cond_);
}
void wait() {
pthrad_cond_wait(&cond_, mutex_.getPthreadMutex());
}
private:
MutexLock& mutex_;
pthread_cond_t cond_;
};
BlockingQueue
#include <deque>
#include <cassert>
#include "MutexLockGuard.h"
#include "Condition.h"
class BlockingQueue {
public:
int dequeue() {
MutexLockGuard guard(mutex);
while (queue.empty()) {
cond.wait(); //自动对mutex解锁并进入等待,当被唤醒时尝试对mutex加锁,如果对mutex加锁成功则判断队列是否为空,由于虚假唤醒现象,这里的while不能换成if
}
assert(!queue.empty()); //双重保障?
int res = queue.front();
queue.pop_front();
return res;
}
void enqueue(int num) {
MutexLockGuard guard(mutex);
queue.push_back(num);
cond.notify(); //也可以放到临界区之外
}
private:
MutexLock mutex;
Condition cond;
std::deque<int> queue;
};
CountDownLatch
CountDownLatch计时器:使用场合如主线程等待多个子线程完成初始化,多个子线程等待主线程的”起跑“命令
#include "MutexLockGuard.h"
#include "Condition.h"
class CountDownLatch {
public:
explicit CountDownLatch(int count) :mutex_(), cond_(mutex_),count_(count) {}
void wait() {
MutexLockGuard guard(mutex_);
while (count_ > 0)
cond_.wait();
}
void countDown() {
MutexLockGuard guard(mutex_);
count_--;
if (count_ == 0)
cond_.notifyAll();
}
private:
MutexLock mutex_; //注意mutex_先于cond_的声明,因为mutex_需要先初始化
Condition cond_;
int count_;
};
线程安全的单例模式
用double checked locking实现单例模式也无法保障线程安全。
用pthread_once_t:int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
功能:本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
template <typename T>
class Singleton:boost::noncopyable {
public:
static T& instance() {
pthread_once(&ponce_, &Singleton::init);
return *value_;
}
private:
Singleton();
~Singleton();
static void init() {
value_ = new T();
}
static pthread_once_t ponce_;
static T* value_;
};
template <typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template <typename T>
T* Singleton<T>::value_ = nullptr;