pthreads:来自临界区内的pthread_cond_signal()

时间:2009-10-28 21:56:39

标签: pthreads mutex signals critical-section

我在线程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);

5 个答案:

答案 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;版本,它似乎更安全。