文章目錄
互斥量
互斥量保護臨界區,任何時候最多隻有一個線程在同一個互斥量保護的臨界區內活動
使用互斥量的原則
- 用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;