16

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

5 回答 5

18

我的建议通常是将pthread_cond_signal()呼叫保持在锁定区域内,但可能不是出于您认为的原因。

在大多数情况下,是否pthread_cond_signal()在持有锁的情况下调用并不重要。Ben 是对的,如果有另一个线程在等待,某些调度程序可能会在释放锁时强制执行上下文切换,因此您的线程可能会在调用pthread_cond_signal(). 另一方面,一些调度程序会在你调用 时立即运行等待线程pthread_cond_signal(),所以如果你在持有锁的情况下调用它,等待线程将被唤醒然后再次进入睡眠状态(因为它现在被互斥锁阻塞)直到信号线程解锁它。确切的行为是高度特定于实现的,并且可能会在操作系统版本之间发生变化,因此它不是您可以依赖的任何东西。

但是,所有这些看起来都超出了您应该关注的主要问题,即代码的可读性和正确性。您不太可能从这种微优化中看到任何实际性能优势(请记住优化的第一条规则:配置文件第一,优化第二)。但是,如果您知道等待线程集不能在设置条件和发送信号的点之间更改,则更容易考虑控制流。否则,您必须考虑诸如“如果线程 A 设置testCondition=TRUE并释放锁,然后线程 B 运行并看到这testCondition是真的,那么它会跳过pthread_cond_wait()并继续重置testConditionFALSE,然后最后线程 A 运行并调用pthread_cond_signal(),这会唤醒线程 C,因为线程 B 实际上并没有在等待,但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_mutexforpthread_cond_wait()调用而不是my_lock.)

于 2009-11-07T05:36:41.840 回答
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将等待 - 可能永远。这就是为什么您要在互斥锁锁定的情况下发出信号。

于 2009-11-19T03:46:01.883 回答
1

两者都是正确的,但是对于反应性问题,大多数调度程序在释放锁时将手交给另一个线程。如果您在解锁之前没有发出信号,那么您的等待线程 A 不在就绪列表中,并且在 B 再次被调度并调用 pthread_cond_signal() 之前,您不会被调度。

于 2009-10-28T22:07:49.430 回答
0

这是关于条件变量的好文章:使用 POSIX 线程条件变量提高应用程序可扩展性的技术(查看“避免互斥争用”部分和第 7 点)

它说,第二个版本可能有一些性能优势。因为它使得具有 pthread_cond_wait 的线程可以减少等待频率。

于 2012-12-20T13:14:43.640 回答
0

Open Group Base Specifications Issue 7 IEEE Std 1003.1, 2013 Edition (据我所知是官方的 pthread 规范)在这个问题上说:

无论线程当前是否拥有调用 pthread_cond_wait() 或 pthread_cond_timedwait() 的线程在等待期间与条件变量相关联的互斥锁,线程都可以调用 pthread_cond_broadcast() 或 pthread_cond_signal() 函数;但是,如果需要可预测的调度行为,则该互斥锁应由调用 pthread_cond_broadcast() 或 pthread_cond_signal() 的线程锁定。

为了增加我的个人经验,我正在开发一个应用程序,该应用程序的代码中条件变量被唤醒的线程破坏(并释放了包含它的内存)。我们发现在多核设备(iPad Air 2)上,如果 pthread_cond_signal() 在互斥锁之外,它实际上可能会崩溃,因为服务员在 pthread_cond_signal 完成之前醒来并销毁了条件变量。这是非常出乎意料的。

所以我肯定会转向“锁内信号”版本,它似乎更安全。

于 2016-04-21T15:44:14.263 回答