深入理解条件变量(虚假唤醒)

深入条件变量

pthread_cond_wait()pthread_cond_signal()的伪实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pthread_cond_wait(mutex,cond) {
// 保存条件变量的值
value = cond->value;
// 解锁传入的已经锁住的互斥量
pthread_mutex_unlock(mutex); // 1
// 这里和pthread_cond_signal/boardcast()有竞争
pthread_mutex_lock(cond->mutex); //2
// 判断竞争谁赢了,我赢了,此线程休眠加入等待队列
if (value == cond->value) {
me->next_cond = cond->waiter;
cond->waiter = me;
pthread_mutex_unlock(cond->mutex);
// 进行睡眠
unable_to_run(me);
}
// 竞争失败,那么就不加入等待队列了,相当于直接(唤醒)
else
pthread_mutex_unlock(cond->mutex);
// 重新锁住传入的互斥量
pthread_mutex_lock(mutex);
}

pthread_cond_signal(cond) {
// 竞争条件
pthread_mutex_lock(cond->mutex); // 2
cond->value++;
// 判断等待队列上是否有线程
if (cond->waiter) {
sleeper = cond->waiter;
cond->waiter = sleeper->next_cond;
//唤醒等待队列上的线程,且移出等待队列
able_to_run(sleeper);
}
pthread_mutex_unlock(cond->mutex);
}

考虑2个线程, A 和 B

线程A执行pthread_cond_wait()后,线程B执行pthread_cond_signal(),此时2个线程同时竞争互斥量cond->mutex(语句2)

线程A在执行完语句1后就尝试进入等待队列,(此时mutex被释放,程序以为线程A已经被挂起,但实际上只是在尝试进入等待队列),

  1. 线程B获得锁cond->mutex,修改条件cond->value后,发现等待队列为空if (cond->waiter)不成立,则释放锁,线程A获得锁,发现条件被改变后又释放锁(因为没有加入到等待队列,又释放了锁,所以看起来像被唤醒了一样,(实际上根本没加入到等待队列中

所以被pthread_cond_wait()唤醒不一定要加入到等待队列中,具体看pthread_cond_signal()执行的时机

  1. 线程A获得锁cond->mutex,然后将自己加入到等待队列中,释放锁后挂起,然后线程B获得锁,if (cond->waiter)成立,将线程A从等待队列中移出(即唤醒)

3个线程 A B C

在上面情况的基础上,若线程C在线程A\B执行前已经在等待队列上,再次重复上面的情况,

  1. 线程A先获得锁,那么还是先加入到等待队列中,然后线程B在唤醒一个,线程C仍在等待队列中
  2. 线程B先获得锁,修改条件,并且把线程C唤醒了,释放锁后,线程A把自己”唤醒了”,

所以说pthread_cond_signal()至少能唤醒一个等待该条件的线程

线程虚假唤醒

在线程B唤醒线程C释放锁mutex后,线程A(pthread_cond_wait)返回,**但是线程C可能修改条件(其他情况也可能导致条件被修改)**,故造成线程被虚假唤醒,

解决方法

1
2
3
4
5
pthread_mutex_lock(&lock);
while (condvar == 0)
// 唤醒之后再次检查条件
pthread_cond_wait(&cond,&lock);
pthread_mutex_unlock(&lock);

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!