为什么pthread_cond_wait会出现伪唤醒?

rbpvctlc  于 2022-12-03  发布在  其他
关注(0)|答案(5)|浏览(177)

引用手册页:
当使用条件变量时,总是有一个布尔 predicate 涉及与每个条件等待相关联的共享变量,如果线程应该继续,则该条件等待为真。()或pthread_cond_wait的值()函数可能会发生。由于从pthread_cond_timedwait返回()或pthread_cond_wait()并不暗示有关此 predicate 值的任何内容,则应在返回此类值时重新计算 predicate 。
因此,即使没有发出信号,pthread_cond_wait也可以返回。这看起来很糟糕。2这就像一个函数随机返回错误的值,或者在它实际到达一个正确的return语句之前随机返回。3这看起来是一个主要的bug。但是,他们选择在手册页中记录这个问题而不是修复它的事实似乎表明,pthread_cond_wait最终被唤醒是有正当理由的很可能,它的运作方式中有某种内在的东西使它无法避免。问题是是什么。

  • 为什么 * pthread_cond_wait会错误地返回?为什么它不能保证只有在收到正确的信号时才会醒来?有人能解释它错误行为的原因吗?
2ic8powd

2ic8powd1#

“伪唤醒”至少有两种含义:

  • 即使在条件下没有呼叫pthread_call_signalpthread_cond_broadcast,在pthread_cond_wait中封锁的执行绪也可以从呼叫传回。
  • 由于调用pthread_cond_signalpthread_cond_broadcastpthread_cond_wait中阻塞的线程返回,但在重新获取互斥锁后,发现基础 predicate 不再为真。

但是,即使条件变量实现不允许前一种情况,后一种情况也可能发生。

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

因此,由于您已经始终需要检查循环下的 predicate ,因此,如果底层条件变量可以有其他类型的伪唤醒,也没有什么区别。

u5i3ibmn

u5i3ibmn2#

下面的解释由大卫·R·布滕霍夫在"Programming with POSIX Threads"(p. 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 ]-----/
yhived7q

yhived7q3#

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

esbemjvw

esbemjvw4#

虽然我认为在设计时没有考虑到这一点,但这里有一个实际的技术原因:结合线程取消,在某些情况下,选择“伪”唤醒可能是绝对必要的,至少除非您愿意对可能的实现策略施加非常非常强的约束。
关键问题是,如果线程在pthread_cond_wait中阻塞时执行取消操作,副作用必须是好像它没有消耗条件变量上的任何信号。(且高度约束),以确保在开始执行取消操作时尚未使用信号,并且在此阶段可能无法将信号“重新发布”到条件变量,因为您可能处于pthread_cond_signal的调用者已经被证明销毁了condvar并释放了它所驻留的内存的情况。
允许伪尾流给你一个简单的出路。如果你可能已经消耗了一个信号,而不是在它到达时继续对取消采取行动,而在一个条件变量上被阻止(或者如果你想偷懒,不管怎样),你可以声明一个伪唤醒已经发生,并成功返回。这完全不会干扰取消操作,因为正确的调用程序在下次循环并再次调用pthread_cond_wait时将简单地对未决取消进行操作。

tquggr8v

tquggr8v5#

我认为伪唤醒的主要原因是**EINTR**。
EINTR中断的函数调用(POSIX.1-2001);参见信号(7)。
来源:https://man7.org/linux/man-pages/man3/errno.3.html,另请参阅
基本上,由pthread_cond_wait()(例如futex(2))调用的系统调用可能会返回EINTR。如果系统调用在内核中被阻塞,并且被POSIX信号中断,通常会发生这种情况(请参阅signal(7))。请参阅www.example.com上的"What is the rationale behind EINTR?"unix.stackexchange.com原因是什么如果系统调用在POSIX信号被传递并由系统调用发出线程处理之后被中断,则(某些)操作系统返回EINTR
我假设一旦用于实现例如pthread_cond_wait()的低级操作系统原语返回EINTR,就存在潜在的竞争条件。pthread_cond_wait()的实现可能不会简单地重新发出系统调用,因为该条件现在可能成立。如果在EINTR之后没有重新评估该条件,那么这很容易导致死锁,在死锁中应用程序没有进一步的进展。

相关问题