为什么pthread_cond_wait有虚假的唤醒?


145

引用手册页:

使用条件变量时,总是存在一个布尔谓词,其中涉及与每个条件等待相关联的共享变量,如果线程应该继续执行,则为true。从pthread_cond_timedwait()或pthread_cond_wait()函数可能会引起虚假的唤醒。由于pthread_cond_timedwait()或pthread_cond_wait()的返回并不暗示此谓词的值,因此应在返回时重新评估该谓词。

因此,pthread_cond_wait即使您未发出信号也可以返回。至少乍一看,这似乎很残酷。就像一个函数,它随机返回错误的值,或者在它真正到达正确的return语句之前随机返回。似乎是一个重大错误。但是他们选择在手册页中记录而不是修复它的事实似乎表明,有一个正当的理由导致pthread_cond_wait虚假地醒来。据推测,它的工作方式具有内在的本质,使它无济于事。问题是什么。

为什么pthread_cond_wait虚假归还?为什么不能保证只有在正确发出信号后才能唤醒它?谁能解释其伪造行为的原因?


5
我以为它与进程捕获到信号时返回都有关。大多数* nix不会在信号中断阻塞调用后重新启动它;他们只是设置/返回表示发生信号的错误代码。
cHao 2011年

1
@cHao:尽管请注意,由于条件变量还是有其他原因导致虚假唤醒,所以处理信号并不是以下错误pthread_cond_(timed)wait:“如果传递了信号,则线程将继续等待条件变量,就像是不中断,否则由于虚假唤醒它将返回零”。其他阻止功能指示EINTR何时被信号中断(例如read)或需要恢复(例如pthread_mutex_lock)。因此,如果没有其他原因导致虚假唤醒,则pthread_cond_wait可以像上述两种情况一样进行定义。
史蒂夫·杰索普

4
维基百科上的相关文章:
虚假


许多功能不能完全完成其工作(I / O中断),并且观察功能可以接收非事件,例如对更改被取消或还原的目录的更改。有什么问题?
curiousguy19年

Answers:


77

David R. Butenhof在“使用POSIX线程编程”(第80页)中给出了以下解释:

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

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

帕特里克·道尔写道: 
>在文章中,汤姆·佩恩(Tom Payne)写道: 
>> Kaz Kylheku写道: 
>>:之所以如此,是因为实现有时无法避免插入 
>>>:这些虚假的唤醒;防止它们可能代价高昂。

>>但是为什么呢?为什么这么难?例如,我们在谈论
>>信号刚到时等待超时的情况? 

>您知道,我想知道pthreads的设计者是否使用了这样的逻辑: 
>条件变量的用户无论如何都必须检查退出条件, 
>因此,如果我们允许,我们不会给他们带来任何额外负担 
>虚假唤醒;并且由于可以想象,允许虚假
>唤醒可以使实现更快,只有在我们 
>允许他们。 

>他们可能没有想到任何特定的实现。 

实际上,您根本相距不远,除非您没有将其推得足够远。 

目的是通过要求谓词循环来强制正确/健壮的代码。这是
由可证明的“核心线程”中正确的学术队伍驱动 
工作组,尽管我认为没有人真的不同意这个意图 
一旦他们了解了它的意思。 

我们遵循了这个意图,并提出了多个层次的理由。首先是
“可靠地”使用循环可保护应用程序免受自身的不完善 
编码惯例。第二个是抽象想象并不难
可以利用此要求进行改进的机器和实现代码 
通过优化操作的平均条件等待操作的性能 
同步机制。 
/ ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
| 康柏计算机公司POSIX线程架构师|
| 我的书:http://www.awl.com/cseng/titles/0-201-63392-2/ |
\ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 


22
基本上这没什么。除了最初的想法“它可以使事情变快”之外,这里没有给出任何解释,但是没人知道它是如何或是否这样做。
Bogdan Ionitza

107

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

  • 一个线程阻塞在pthread_cond_wait从调用返回,即使没有来电pthread_call_signalpthread_cond_broadcast发生的条件。
  • pthread_cond_wait由于调用pthread_cond_signalpthread_cond_broadcast,阻塞在返回中的线程返回,但是在重新获取互斥量之后,发现基础谓词不再为真。

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

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

因此,由于您已经一直需要在循环下检查谓词,因此基础条件变量是否可以具有其他种类的虚假唤醒没有区别。


23
是。本质上,当使用事件而不是带有计数的同步机制时,会发生这种情况。可悲的是,似乎POSIX信号量(无论如何在Linux上)也受到大量唤醒的影响。我只是觉得有点奇怪,因为同步原语的基本功能故障仅被接受为“正常”,并且必须在用户级别解决:(大概,如果记录了系统调用,则开发人员将齐心协力。带有“伪造的段错误”部分,或者可能是“虚假的连接到错误的URL”或“虚假的打开错误的文件”
Martin James

2
“虚假唤醒”的更常见情况很可能是调用pthread_cond_broadcast()的副作用。假设您有一个由5个线程组成的池,其中两个唤醒广播并开始工作。其他三个醒来,发现工作已经完成。多处理器系统还可能导致条件信号意外唤醒多个线程。该代码只是再次检查谓词,看到无效状态,然后返回睡眠状态。无论哪种情况,检查谓词都可以解决问题。IMO通常,用户不应使用原始POSIX互斥体和条件。
CubicleSoft

1
@MartinJames-经典的“虚假” EINTR怎么样?我会同意,不断地在循环中测试EINTR有点烦人,并且使代码相当难看,但是开发人员还是会这样做以避免随机破坏。
CubicleSoft

2
@Yola不行,因为您应该将互斥锁锁定,pthread_cond_signal/broadcast并且您将无法这样做,直到通过调用解锁互斥锁为止pthread_cond_wait
a3f

1
这个答案的例子非常现实,我同意检查谓词是一个好主意。但是,不能通过采取有问题的步骤“线程1完成其当前任务,并返回队列进行更多工作”并用“线程1完成其当前任务,然后回到等待状态”来解决问题。条件变量”?这将消除答案中描述的故障模式,并且我很确定,在没有虚假唤醒的情况下,它将使代码正确。在实践中,是否有任何实际的实现会产生虚假的唤醒?
Quuxplusone

7

pthread_cond_signal中的“通过条件信号进行的多次唤醒”部分提供了pthread_cond_wait和pthread_cond_signal的示例实现,其中涉及虚假的唤醒。


2
就目前而言,我认为这个答案是错误的。该页面上的示例实现具有“ notify one”的实现,等同于“ notify all”;但它似乎并未产生实际的虚假唤醒。线程唤醒的唯一方法是通过其他某个线程调用“全部通知”,或通过某些其他线程调用标记为“通知一个”的东西(实际上是“全部通知”)。
Quuxplusone

5

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.