条件变量的使用

条件变量的正确使用方式一般为:

  1. 必须与mutex一起使用,该布尔表达式的读写需要受到mutex的保护
  2. 在mutex已上锁的时候才能用 wait()
  3. 吧判断布尔条件和wait()放到while循环中

写成代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mudo::MutexLock mutex;
mudo::Condition cond(mutex);
std::dequeue<int> queue;

int dequeue(){
MutexLockGuard lock(mutex);
while(queue.empty()){
cond.wait(); //会原子的unlock mutex进入等待,不会与enqueue形成死锁,当wait出来后,会自动加锁
}
assert(!queue.empty());
int top = queue.front();
queue.pop_front();
return top;
}

使用while循环来等待条件变量,而不能使用if的原因就是在linux下有时会出现虚假唤醒,在多核的情况下,一个noify有可能会唤醒多个wait函数,造成虚假唤醒。还有一个原因就是,如果使用if语句的话,那么有可能在enqueue的时候不小心使用了broadcast,造成所有等待wait的线程被唤醒,每一个线程都加锁后进行pop操作,就有可能导致队列为空的情况下还在pop,从而出现错误。

对于signal和broadcast端:

  1. 在signal之前一定要修改布尔表达式
  2. 修改布尔表达式要在mutex的保护下
  3. 注意区分signal和broadcast:broadcast通常用于表明状态变化,signal通常用于表示资源可用

代码为:

1
2
3
4
5
void enqueue(int x){
MutexLockGuard lock(mutex);
queue.push_back(x);
cond.notify() //notify可以移出临界区之外
}

在上面的代码中,enqueue每一次push都会调用notify,能不能在队列从0变为1的时候才调用notify?

对于只有一个dequeue的线程的话可以这么做,但对于有多个dequeue的话,就不行。因为只有在0变1
的情况下调用notify,此时只有一个线程被唤醒,即使队列有很多资源可以使用,其他线程也会一直
阻塞在wait里面。造成线程浪费,程序并发性得不到应用。