调用pthread_cond_signal而不锁定互斥锁


82

我读到某个地方,我们应该在调用pthread_cond_signal之前锁定互斥锁,并在调用它之后解锁mutext:

pthread_cond_signal()例程用于发信号(或唤醒)另一个正在等待条件变量的线程。它应该在互斥锁锁定后调用,并且必须解锁互斥锁才能完成pthread_cond_wait()例程。

我的问题是:在不锁定互斥锁的情况下调用pthread_cond_signalpthread_cond_broadcast方法是否可以?

Answers:


140

如果未将互斥锁锁定在更改条件和信号的代码路径中,则可能会丢失唤醒。考虑这对过程:

流程A:

pthread_mutex_lock(&mutex);
while (condition == FALSE)
    pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

进程B(不正确):

condition = TRUE;
pthread_cond_signal(&cond);

然后考虑这种可能的指令交织,condition开始于FALSE

Process A                             Process B

pthread_mutex_lock(&mutex);
while (condition == FALSE)

                                      condition = TRUE;
                                      pthread_cond_signal(&cond);

pthread_cond_wait(&cond, &mutex);

现在conditionTRUE,但是进程A滞留在条件变量上等待-它错过了唤醒信号。如果我们更改进程B来锁定互斥锁:

进程B(正确):

pthread_mutex_lock(&mutex);
condition = TRUE;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

...那么上述情况就不会发生;唤醒将永远不会被错过。

(请注意,您实际上可以pthread_cond_signal()自身移动到之后pthread_mutex_unlock(),但是这可能导致线程的最佳调度不太理想,并且由于更改了条件本身,您已经锁定了该代码路径中的互斥锁)。


3
@Nemo:是的,pthread_signal_cond() 可以在互斥锁解锁后移动“正确”路径,尽管最好不要这样做。也许更正确地说,在您要调用的位置pthread_signal_cond(),您已经需要锁定互斥锁才能修改条件本身。
caf

@Nemo:是的,代码已损坏,这就是为什么它被标记为“不正确”的原因-我的经验是,提出这个问题的人经常考虑完全将锁定留在信令路径上。也许您的经历有所不同。
caf

8
对于它的价值,似乎是在信号之前或之后解锁的问题实际上是很重要的。之后解锁可以确保较低优先级的线程无法从较高优先级的线程中窃取该事件,但是如果您不使用优先级,则在信号之前进行解锁实际上会减少系统调用/上下文切换和改善整体表现。
R .. GitHub停止帮助ICE,

1
@R ..向后。在信号增加系统调用次数并降低整体性能之前解锁。如果在解锁之前发出信号,则实现会知道该信号不可能唤醒线程(因为cv上阻塞的任何线程都需要互斥体来进行前进),从而允许其使用快速路径。如果在解锁后发出信号,则解锁和信号都会唤醒线程,这意味着它们都是昂贵的操作。
David Schwartz

1
@Alceste_:请参见POSIX中的文本:“当线程等待条件变量时,已为pthread_cond_timedwait()pthread_cond_wait()操作指定了特定的互斥量,则该互斥量和条件变量之间就会形成动态绑定,只要该绑定在条件变量上至少有一个线程被阻塞。在此期间,未定义任何线程尝试使用不同互斥量等待该条件变量的效果。
caf

48

根据本手册:

pthread_cond_broadcast()pthread_cond_signal()功能 可以通过它是否当前拥有互斥线程调用的线程调用pthread_cond_wait()pthread_cond_timedwait()已与在他们等待的条件变量相关联; 但是,如果需要可预测的调度行为,则该互斥锁应由线程调用pthread_cond_broadcast()或 锁定 pthread_cond_signal()

可预测调度行为声明的含义由comp.programming.threads上的Dave Butenhof(使用POSIX线程编程的作者)解释了,并在此处提供


6
+1用于链接来自Dave Butenhof的邮件。我自己一直想知道这个问题,现在我知道了……今天学到了一些重要的东西。谢谢。
尼尔斯·派宾布林克

如果需要可预测的调度行为,则将语句按所需顺序放在一个线程中,或使用线程优先级。
卡兹(Kaz)

7

caf,在示例代码中,流程B进行了修改,condition而没有先锁定互斥锁。如果进程B只是在修改期间锁定了互斥锁,然后在调用之前仍然解锁了互斥锁pthread_cond_signal,那将没有问题---我是否正确?

我直觉上相信caf的位置是正确的:pthread_cond_signal不拥有互斥锁就打电话是个坏主意。但是,caf的榜样实际上并不是支持这一立场的证据。这只是证明弱点(实际上不言而喻)的证据,除非您先锁定了该互斥锁,否则修改受互斥锁保护的共享状态是一个坏主意。

谁能提供一些示例代码,其中调用pthread_cond_signalpthread_mutex_unlock产生正确的行为,但调用pthread_mutex_unlockpthread_cond_signal产生错误的行为?


3
其实,我觉得我的问题是重复的这一个,得到的回答是“没关系,你可以完全调用pthread_cond_signal会不拥有互斥锁。有没有正确的问题。但在常见的实现,你就会错过一个聪明的优化在pthread的深处,因此在保持锁的情况下调用pthread_cond_signal稍微好一些。
Quuxplusone 2012年

我在回答的最后一段中做了这一观察。
caf 2013年

您在这里有一个不错的方案:stackoverflow.com/a/6419626/1284631请注意,它并不声称该行为是错误的,它仅表示该行为可能与预期不符的情况。
user1284631 2014年

这是可能的,其中调用创建示例代码pthread_cond_signal后,pthread_mutex_unlock可能会导致丢失唤醒,因为信号被捕获是“错误”的线程,一个看到的谓词在更改之后受阻。仅当同一条件变量可用于多个谓词并且您不使用时pthread_cond_broadcast,这才是问题,无论如何这是一种罕见且脆弱的模式。
David Schwartz
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.