为什么pthread_cond_wait有虚假的唤醒?

时间:2011-12-21 18:30:42

标签: c pthreads

引用手册页:

  

使用条件变量时,总会有一个布尔谓词,涉及与每个条件等待关联的共享变量,如果线程应该继续,则为true。可能会发生pthread_cond_timedwait()或pthread_cond_wait()函数的虚假唤醒。由于从pthread_cond_timedwait()或pthread_cond_wait()返回并不意味着有关此谓词的值的任何内容,因此应在返回时重新评估谓词。

因此,即使您没有发信号,pthread_cond_wait也可以返回。乍一看,这看起来非常残酷。它就像一个随机返回错误值或在实际到达正确的返回语句之前随机返回的函数。这似乎是一个主要的错误。但是他们选择在手册页中记录这一点而不是修复它的事实似乎表明,pthread_cond_wait最终虚假地唤醒是有正当理由的。据推测,它有一些关于它是如何工作的固有的东西使得它无法帮助。问题是什么。

为什么 pthread_cond_wait虚假地返回?为什么它不能保证它只是在它被正确发出信号后醒来?任何人都能解释其虚假行为的原因吗?

4 个答案:

答案 0 :(得分:93)

至少有两件事'虚假唤醒'可能​​意味着:

  • pthread_cond_wait中被阻止的线程可以从通话中返回,即使在此情况下没有呼叫信号或广播。
  • 由于对信号或广播的调用,在pthread_cond_wait中被阻止的线程返回,但是在重新获取互斥锁之后,发现基础谓词不再成立。

但即使条件变量实现不允许前一种情况,也会发生后一种情况。考虑生产者消费者队列和三个线程。

  • 线程1刚刚将一个元素队列化并释放了互斥锁,队列现在为空。该线程正在对它在某些CPU上获取的元素执行任何操作。
  • 线程2尝试使某个元素出列,但在互斥锁下检查时调用队列为空,调用pthread_cond_wait,并在等待信号/广播的呼叫中阻塞。
  • 线程3获取互斥锁,将新元素插入队列,通知条件变量,并释放锁定。
  • 为响应来自主题3的通知,正在等待条件的线程2计划运行。
  • 但是,在线程2设法进入CPU并获取队列锁之前,线程1完成其当前任务,并返回队列以进行更多工作。它获取队列锁,检查谓词,并发现队列中有工作。它继续将线程3插入的项目出列,释放锁定,并对线程3排队的项目执行任何操作。
  • 线程2现在进入CPU并获得锁定,但是当它检查谓词时,它发现队列为空。线程1'偷走'该项目,因此唤醒似乎是虚假的。线程2需要再次等待条件。

因此,您已经总是需要在循环下检查谓词,如果基础条件变量可以有其他类型的虚假唤醒,则没有区别。

答案 1 :(得分:74)

David R. Butenhof在"Programming with POSIX Threads"(第80页)中给出了以下解释:

  

虚假唤醒可能听起来很奇怪,但在某些多处理器系统上,使条件唤醒完全可预测可能会大大减慢所有条件变量操作。

在下面的comp.programming.threads discussion中,他扩展了设计背后的思想:

Patrick Doyle wrote: 
> In article , Tom Payne   wrote: 
> >Kaz Kylheku  wrote: 
> >: It is so because implementations can sometimes not avoid inserting 
> >: these spurious wakeups; it might be costly to prevent them. 

> >But why?  Why is this so difficult?  For example, are we talking about 
> >situations where a wait times out just as a signal arrives? 

> You know, I wonder if the designers of pthreads used logic like this: 
> users of condition variables have to check the condition on exit anyway, 
> so we will not be placing any additional burden on them if we allow 
> spurious wakeups; and since it is conceivable that allowing spurious 
> wakeups could make an implementation faster, it can only help if we 
> allow them. 

> They may not have had any particular implementation in mind. 

You're actually not far off at all, except you didn't push it far enough. 

The intent was to force correct/robust code by requiring predicate loops. This was 
driven by the provably correct academic contingent among the "core threadies" in 
the working group, though I don't think anyone really disagreed with the intent 
once they understood what it meant. 

We followed that intent with several levels of justification. The first was that 
"religiously" using a loop protects the application against its own imperfect 
coding practices. The second was that it wasn't difficult to abstractly imagine 
machines and implementation code that could exploit this requirement to improve 
the performance of average condition wait operations through optimizing the 
synchronization mechanisms. 
/------------------[ David.Buten...@compaq.com ]------------------\ 
| Compaq Computer Corporation              POSIX Thread Architect | 
|     My book: http://www.awl.com/cseng/titles/0-201-63392-2/     | 
\-----[ http://home.earthlink.net/~anneart/family/dave.html ]-----/ 

答案 2 :(得分:7)

pthread_cond_signal中的“条件信号多次唤醒”部分有一个pthread_cond_wait和pthread_cond_signal的示例实现,它涉及虚假唤醒。

答案 3 :(得分:3)

虽然我认为在设计时并未考虑过,但这是一个实际的技术原因:与线程取消结合使用时,在某些情况下,绝对有必要选择“虚假唤醒”选项,至少除非您愿意对可能的实施策略施加非常严格的约束。

关键问题在于,如果一个线程在pthread_cond_wait中被阻塞时执行取消操作,那么副作用就好像它没有消耗条件变量上的任何信号一样。但是,要确保您在开始执行消除操作时尚未消耗信号就非常困难(且高度约束),并且在此阶段可能无法将信号“重新发布”到条件变量,因为您可能处于pthread_cond_signal的调用者已经被证明销毁了condvar并释放了它所驻留的内存的情况下。

虚假唤醒的余量让您轻松自在。如果您可能已经消耗了一个信号(或者无论如何都想变得懒惰),则可以声明一个虚假的唤醒,而不是在条件变量阻止时继续执行取消操作,而不必声明取消操作,并成功返回。这完全不会干扰取消操作,因为正确的调用者将在下一次循环并再次调用pthread_cond_wait时对未决的取消操作。