虚假唤醒(spurious wakeup)

linux对虚假唤醒的解释

One a multi-processor, it may be impossible for an implementation of pthread_cond_signal() to avoid the unblocking of more one thread blocked on a condition variable.
The effect is that more than on thread can reture from its call to pthread_cond_wait() or pthread_cond_timedwait() as a result of one call to pthread_cond_signal(). This effiect is called "spurious wakeup". Note that the situation is self-correcting in that the number if threads that are so awakened is finite; for example, the next thread to call pthread_cond_wait() after the sequence of events above blocks.
While this problem could be resolved, the loss of efficiency for a fringe condition that occurs only rarely is unacceptable, especially given that one has to check the predicate associated with a condition variable anyway. Correcting this problem would unnecessarily reduce degree of concurrency in this basic building block for all higher-level synchronization operations.

总结上面这段话:

在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。结果是,当一个线程调用phread_cond_signal后,多个调用pthread_cond_wait或者pthread_cond_timewait的线程返回。这种效应就叫做"虚假唤醒"。

针对于这种情况,官方给出的解释是: 虽然虚假唤醒在pthread_cond_wait函数中可以解决, 但是为了发生概率很低的情况而降低边缘条件效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。通常的建议是将if改为while

static void *thread_func(void *arg)
{
	while (1) {
		pthread_mutex_lock(&mtx);           //这个mutex主要是用来保证pthread_cond_wait的并发性
		while (msg_list.empty())   {     //pthread_cond_wait里的线程可能会被意外唤醒(虚假唤醒),如果这个时候,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait
			pthread_cond_wait(&cond, &mtx);
		}
		msg = msg_list.pop();
		pthread_mutex_unlock(&mtx);             //临界区数据操作完毕,释放互斥锁
		// handle msg
	}
	return 0;
}

通俗解释虚假唤醒

比如我们现在有一个生产者-消费者队列和三个线程:

  • 1号线程从队列中获取了一个元素,此时队列变为空。
  • 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。
  • 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。
  • 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
  • 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。
  • 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。

为什么使用while而不是if

如果用if判断,多个等待线程在满足if条件时都会被唤醒(虚假的),但实际上条件并不满足,生产者生产出来的消费品已经被第一个线程消费了。

这就是我们使用while去做判断而不是使用if的原因:因为等待在条件变量上的线程被唤醒有可能不是因为条件满足而是由于虚假唤醒。所以,我们需要对条件变量的状态进行不断检查直到其满足条件,不仅要在pthread_cond_wait前检查条件是否成立,在pthread_cond_wait之后也要检查。


标题:虚假唤醒(spurious wakeup)
作者:reyren
地址:https://www.reyren.cn/articles/2021/06/04/1622789349658.html

    评论
    0 评论
avatar

取消