第二章:線程同步精要


互斥量

互斥量保護臨界區,任何時候最多隻有一個線程在同一個互斥量保護的臨界區內活動

使用互斥量的原則

  • 用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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章