线程同步注意事项

互斥器(mutex)

互斥器主要是为了保护共享数据的,保证同一时刻只有一个线程可以操作

  • 用RAII手法封装mutex(创建、销毁、加锁、解锁)
  • 只用非递归的mutex(就是不可重入的mutex)
  • 不手动调用lock和unlock函数,参照第一点的方式
  • 在每次构造MutexLockGuard的时候,思考调用栈上已经持有的锁,防止加锁顺序不同导致死锁
  • 不使用跨进程的mutex,进程间通信尽量只用TCP Sockets
  • 加锁、解锁在同一个线程中完成,线程A不去unlock线程B已经锁住的mutex(RAII可以自动保证)
  • 必要的时候可以用PTHREAD_MUTEX_ERRORCHECK来排查错误

条件变量(condition variable)

如果需要等待某个条件成立,我们应该使用条件变量。一般是一个或多个线程等待某个表达式为真,即等待别的线程唤醒自己

  1. 对于wait一端:
  • 必须与mutex一起使用,该表达式的读写需要收到这个mutex的保护
  • 在mutex加锁时才能调用wait
  • 把判断表达式的条件和wait放到while循环中去

例子

MutexLock mutex;
Condition cond(mutex);
std::deque<int> deq;

int dequeue()
{
	MutexLockGuard lock(mutex);
	while (deq.empty())  //此处必须用循环,不能用if条件(if可能会虚假唤醒);必须在判断之后再wait()
	{
		cond.wait();  //这一步会unlock mutex 并进入等待,不会和enqueue死锁
		//wait结束后,会自动加锁
	}
	assert(!deq.empty());
	int top = deq.front();
	deq.pop_front();
	return top;
}

  1. 对于signal/broadcast一端:
  • 在signal之前要修改表达式
  • 修改表达式通常要用mutex保护
  • 注意区分signal和broadcast:signal通常表示资源可用,broadcast通常表示状态变化

例子

void enqueue(int var)
{
	MutexLockGuard lock(mutex);
	queue.push_back(var);
	cond.notify();  //换成notifyAll会有惊群现象,其中一个拿了锁并拿走了task,其余无事可做又睡了过去
}

总结

两点想法
1.应该先把程序写正确(并尽量保持清晰和简单),单后再考虑性能优化,如果确实还有必要优化的话。
2.让一个正确的程序变快,远比让一个快的程序便正确容易的多

猜你喜欢

转载自blog.csdn.net/tsh123321/article/details/88966691
今日推荐