我在线程A中有以下代码,它使用pthread_cond_wait()
pthread_mutex_lock(&my_lock);
if ( false == testCondition )
pthread_cond_wait(&my_wait,&my_lock);
pthread_mutex_unlock(&my_lock);
我在线程B中有以下代码,它代表线程A
pthread_mutex_lock(&my_lock);
testCondition = true;
pthread_cond_signal(&my_wait);
pthread_mutex_unlock(&my_lock);
如果没有其他线程,如果将pthread_cond_signal(&my_wait)
移出临界区块,它会有什么不同吗?
pthread_mutex_lock(&my_lock);
testCondition = true;
pthread_mutex_unlock(&my_lock);
pthread_cond_signal(&my_wait);
答案 0 :(得分:17)
我的建议通常是将pthread_cond_signal()
电话保留在锁定区域内,但可能不是您认为的原因。
在大多数情况下,无论是否持有锁,都要致电pthread_cond_signal()
并不重要。 Ben是正确的,如果有另一个线程等待,一些调度程序可能会在释放锁定时强制进行上下文切换,因此您的线程可能会在调用pthread_cond_signal()
之前被切换掉。另一方面,一旦调用pthread_cond_signal()
,一些调度程序将运行等待线程,所以如果你在锁定时调用它,等待线程将被唤醒,然后再回到睡眠状态(因为它现在是在互斥锁上被阻塞)直到信令线程解锁它。确切的行为是高度特定于实现的,并且可能在操作系统版本之间发生变化,因此它不是您可以依赖的任何内容。
但是,所有这些看起来都超过了您应该关注的主要问题,即代码的可读性和正确性。您不太可能从这种微优化中看到任何实际的性能优势(请记住优化的第一条规则:首先配置文件,优化第二条规则)。但是,如果您知道等待线程集在您设置条件的点和发送信号之间无法更改,则更容易考虑控制流。否则,您必须考虑“如果线程A设置testCondition=TRUE
并释放锁定,然后线程B运行并看到testCondition
为真,那么它会跳过pthread_cond_wait()
然后继续将testCondition
重置为FALSE
,最后线程A运行并调用pthread_cond_signal()
,这会唤醒线程C,因为线程B实际上没有等待,但是{{1}不再是真的“。这很令人困惑,并且可能导致代码中难以诊断的竞争条件。出于这个原因,我认为最好用锁定信号发出信号;这样,你知道设置条件和发送信号是相互原子的。
在相关说明中,您拨打testCondition
的方式不正确。 pthread_cond_wait()
有可能(尽管很少见)返回而实际上没有条件变量发出信号,并且还有其他情况(例如,我上面描述的竞赛),即使条件,信号也可能最终唤醒线程不是真的。为了安全起见,您需要将pthread_cond_wait()
调用放在一个测试条件的pthread_cond_wait()
循环中,以便在您之后不满足条件时回调while()
重新获得锁定。在您的示例中,它看起来像这样:
pthread_cond_wait()
(我还更正了原始示例中可能是拼写错误的内容,即使用pthread_mutex_lock(&my_lock);
while ( false == testCondition ) {
pthread_cond_wait(&my_wait,&my_lock);
}
pthread_mutex_unlock(&my_lock);
代替my_mutex
来代替pthread_cond_wait()
。)
答案 1 :(得分:2)
等待条件变量的线程应该保持互斥锁被锁定,而另一个线程应始终在锁定互斥锁时发出信号。这样,当您发送信号时,知道另一个线程正在等待该条件。否则,等待线程可能看不到发出信号的情况,并且会无限期地阻止它。
条件变量通常使用如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int go = 0;
void *threadproc(void *data) {
printf("Sending go signal\n");
pthread_mutex_lock(&lock);
go = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
}
int main(int argc, char *argv[]) {
pthread_t thread;
pthread_mutex_lock(&lock);
printf("Waiting for signal to go\n");
pthread_create(&thread, NULL, &threadproc, NULL);
while(!go) {
pthread_cond_wait(&cond, &lock);
}
printf("We're allowed to go now!\n");
pthread_mutex_unlock(&lock);
pthread_join(thread, NULL);
return 0;
}
这是有效:
void *threadproc(void *data) {
printf("Sending go signal\n");
go = 1;
pthread_cond_signal(&cond);
}
但是,请考虑main
while(!go) {
/* Suppose a long delay happens here, during which the signal is sent */
pthread_cond_wait(&cond, &lock);
}
如果该评论所描述的延迟发生,pthread_cond_wait
将等待 - 可能永远。这就是您希望锁定互斥锁时发出信号的原因。
答案 2 :(得分:1)
两者都是正确的,但是对于反应性问题,大多数调度程序在释放锁时将手提供给另一个线程。我在解锁之前没有发出信号,你的等待线程A不在就绪列表中,并且只有在再次安排B并调用pthread_cond_signal()之后才会安排thous。
答案 3 :(得分:0)
关于条件变量的详细说明如下:Techniques for Improving the Scalability of Applications Using POSIX Thread Condition Variables(请参阅“避免互斥争用”部分和第7点)
它说,第二个版本可能会有一些性能优势。因为pthread_cond_wait的线程可以不那么频繁地等待。
答案 4 :(得分:0)
Open Group Base Specifications Issue 7 IEEE Std 1003.1, 2013 Edition(据我所知是官方的pthread规范)在此事上说明了这一点:
pthread_cond_broadcast()或pthread_cond_signal()函数可能是 线程调用它是否当前拥有互斥锁 调用pthread_cond_wait()或pthread_cond_timedwait()的线程有 在等待期间与条件变量相关联;但是,如果 需要可预测的调度行为,那么互斥量应该是 由调用pthread_cond_broadcast()或的线程锁定 调用pthread_cond_signal()。
为了增加我的个人经验,我正在开发一个应用程序,该应用程序具有被唤醒的线程销毁条件变量(以及释放包含它的内存)的代码。我们发现在多核设备(iPad Air 2)上,pthread_cond_signal()有时会在互斥锁之外崩溃,因为服务员在pthread_cond_signal完成之前醒来并销毁了条件变量。这是非常意外的。
所以我肯定会转向锁定内部的信号&#39;版本,它似乎更安全。