【问题标题】:Why does pthread_cond_wait have spurious wakeups?为什么 pthread_cond_wait 有虚假唤醒?
【发布时间】:2012-01-25 13:07:02
【问题描述】:

引用手册页:

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

所以,pthread_cond_wait 可以返回,即使你没有发出信号。至少乍一看,这似乎很残酷。这就像一个随机返回错误值或在实际到达正确的返回语句之前随机返回的函数。这似乎是一个重大错误。但他们选择在手册页中记录这一点而不是修复它的事实似乎表明pthread_cond_wait 最终被虚假唤醒是有正当理由的。据推测,它的工作原理有一些内在的东西使它无法帮助。问题是什么。

为什么pthread_cond_wait 会虚假返回?为什么它不能保证只有在收到正确的信号时才会醒来?谁能解释其虚假行为的原因?

【问题讨论】:

  • 我想它与进程捕获信号时返回有关。大多数 *nixes 在信号中断后不会重新启动阻塞调用;他们只是设置/返回一个错误代码,表明发生了信号。
  • @cHao:虽然请注意,因为条件变量有其他原因导致虚假唤醒,处理信号对于pthread_cond_(timed)wait来说不是错误:“如果信号被传递......线程继续等待条件变量,就好像它没有被中断一样,或者由于虚假唤醒它应该返回零”。其他阻塞函数在被信号中断(例如read)或需要恢复(例如pthread_mutex_lock)时指示EINTR。因此,如果没有其他原因导致虚假唤醒,pthread_cond_wait 可能会被定义为其中任何一个。
  • 维基百科的相关文章:Spurious wakeup
  • 许多函数不能完全完成它们的工作(中断的 I/O)并且观察函数可以接收到非事件,比如更改被取消或恢复的目录的更改。有什么问题?

标签: c pthreads


【解决方案1】:

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

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

允许虚假唤醒让您轻松摆脱困境。如果您可能已经消耗了一个信号(或者如果您想变得懒惰,无论如何),您可以声明一个虚假的唤醒已经发生,而不是在它到达时继续执行取消操作,而不是在条件变量上阻塞,并成功返回。这根本不会干扰取消操作,因为正确的调用者将在下一次循环并再次调用 pthread_cond_wait 时简单地对挂起的取消进行操作。

【讨论】:

    【解决方案2】:

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

    • 即使没有对 pthread_call_signalpthread_cond_broadcast 的调用发生,在 pthread_cond_wait 中阻塞的线程也可以从调用中返回。
    • 由于调用pthread_cond_signalpthread_cond_broadcast 而在pthread_cond_wait 中阻塞的线程返回,但是在重新获取互斥锁后,发现底层谓词不再为真。

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

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

    因此,由于您始终需要在循环下检查谓词,因此底层条件变量是否可以具有其他类型的虚假唤醒也没有什么区别。

    【讨论】:

    • 是的。本质上,这就是使用事件而不是使用计数的同步机制时发生的情况。可悲的是,POSIX 信号量(无论如何在 Linux 上)似乎也受到虚假唤醒的影响。我只是觉得有点奇怪,同步原语的基本功能失败只是被接受为“正常”并且必须在用户级别解决:(如果记录了系统调用,开发人员可能会准备好带有“虚假段错误”部分,或者可能是“虚假连接到错误的 URL”或“虚假打开错误文件”。
    • 更常见的“虚假唤醒”场景很可能是调用 pthread_cond_broadcast() 的副作用。假设您有一个由 5 个线程组成的池,其中两个在广播中醒来并开始工作。其他三个人醒来,发现工作已经完成。多处理器系统也可能导致意外唤醒多个线程的条件信号。代码只是再次检查谓词,发现无效状态,然后重新进入睡眠状态。无论哪种情况,检查谓词都可以解决问题。 IMO,一般来说,用户不应该使用原始的 POSIX 互斥锁和条件。
    • @MartinJames - 经典的“虚假”EINTR 怎么样?我同意在循环中不断测试 EINTR 有点烦人,并且使代码相当难看,但开发人员无论如何都会这样做以避免随机损坏。
    • @Yola 不,它不能,因为您应该在 pthread_cond_signal/broadcast 周围锁定一个互斥锁,而您将无法这样做,直到通过调用 pthread_cond_wait 解锁互斥锁.
    • 这个答案的例子非常现实,我同意检查谓词是个好主意。但是,通过采取有问题的步骤“线程 1 完成其当前任务,并返回队列以进行更多工作”并将其替换为“线程 1 完成其当前任务,并返回等待条件变量”?这将消除答案中描述的故障模式,并且我很确定它会使代码正确,在没有虚假唤醒的情况下。在实践中是否有任何实际实现会产生虚假唤醒?
    【解决方案3】:

    pthread_cond_signal 中的“按条件信号进行多次唤醒”部分包含 pthread_cond_wait 和 pthread_cond_signal 的示例实现,其中涉及虚假唤醒。

    【讨论】:

    • 我认为这个答案是错误的,就目前而言。该页面上的示例实现具有“通知一个”的实现,相当于“通知所有”;但它似乎并没有真正产生 spurious 唤醒。线程唤醒的唯一方法是通过其他线程调用“通知所有”,或通过其他线程调用标记为“通知一个”的其他线程,即“通知所有”。跨度>
    【解决方案4】:

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

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

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

    帕特里克·多伊尔写道: > 在文章中,汤姆佩恩写道: > >Kaz Kylheku 写道: > >:之所以如此,是因为实现有时无法避免插入 > >:这些虚假的唤醒;阻止它们可能代价高昂。 > >但是为什么呢?为什么这么难?例如,我们在谈论 > >信号到达时等待超时的情况? > 你知道,我想知道 pthread 的设计者是否使用了这样的逻辑: > 条件变量的用户无论如何都必须在退出时检查条件, > 因此,如果我们允许,我们不会给他们带来任何额外的负担 > 虚假唤醒;并且因为可以想象允许虚假 > 唤醒可以使实现更快,只有当我们 > 允许他们。 > 他们可能没有考虑任何特定的实施。 你实际上一点也不远,只是你没有把它推得足够远。 目的是通过要求谓词循环来强制执行正确/健壮的代码。这是 由“核心线索”中可证明正确的学术队伍推动 工作组,虽然我认为没有人真的不同意这个意图 一旦他们明白这意味着什么。 我们遵循了这一意图,并提出了几个层次的理由。第一个是 “虔诚地”使用循环保护应用程序免受自身不完美的影响 编码实践。二是不难抽象想象 可以利用此要求改进的机器和实现代码 通过优化平均条件等待操作的性能 同步机制。 /--------------------[大卫.Buten...@compaq.com]------\ | Compaq Computer Corporation POSIX 线程架构师 | |我的书:http://www.awl.com/cseng/titles/0-201-63392-2/ | \-----[ http://home.earthlink.net/~anneart/family/dave.html ]-----/

    【讨论】:

    • 基本上这没什么。除了最初的想法是“它可能使事情变得更快”之外,这里没有给出任何解释,但没有人知道它是如何实现的,或者根本不知道它是否会实现。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-02-16
    相关资源
    最近更新 更多