第二章:线程同步精要


互斥量

互斥量保护临界区,任何时候最多只有一个线程在同一个互斥量保护的临界区内活动

使用互斥量的原则

  • 用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;

用shared_ptr实现copy-on-write

用shared_ptr和互斥量实现读写锁

copy-on-write图示:update()函数内部shared_ptr data_的变化

  • shared_ptr data的出现是因为读线程调用了query(), query()调用getData()导致复制了data_, 对原数据的引用计数加1.
  • shared_ptr newData是个局部变量,出了if作用域就被销毁,因此原数据的引用计数减1
  • 当读线程对原数据的读取完成后(出query()作用域),局部变量data也被销毁,原数据的引用计数减为0,因此原数据的内存被释放
  • 在先开辟的内存上做修改,但由于通过swap()使得data_指向这个新开辟的内存,因此后续的访问能访问到这个新开辟的内存
    copy-on-write图示1
    copy-on-write图示2
    在这里插入图片描述
class CustomerData : boost::noncopyable {
public:
	CustomerData() : data_(new Map) {}
	int query(const string& customer, const string& stock) const;
private:
	typedef pair<string, int> Entry;
	typedef vector<Entry> EntryList;
	typedef map<string, EntryList> Map;
	typedef shared_ptr<Map> MapPtr;

	MapPtr getData() const {
		MutexLockGuard lock(mutex_);
		return data_;
	}
	static int findEntry(const EntryList& entries, const string& stock);
	void update(const string& customer, const EntryList& entries);

	mutable MutexLock mutex_;
	MapPtr data_;

};
int CustomerData::query(const string& customer, const string& stock) const {
	MapPtr data = getData();  //引用计数加1
	Map::const_iterator entries = data->find(customer);
	if (entries != data->end()) {
		return findEntry(entries->second, stock);
	else
		return -1;
	}
}
void CustomerData::update(const string& customer, const EntryList& entries) {
	MutexLockGuard lock(mutex_);
	if (!data_.unqiue()) {
		MapPtr newData(new Map(*data_)); //copy and swap
		data_.swap(newData);
	}
	assert(data_.unique());
	(*data_[customer]) = entries;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值