【问题标题】:pthreads : pthread_cond_signal() from within critical sectionpthreads:来自临界区的 pthread_cond_signal()
【发布时间】:2010-12-11 01:19:52
【问题描述】:

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

【问题讨论】:

  • 虚假唤醒是pthread_cond_wait 规范的一部分。如果没有其他线程发出信号,它可能会返回。永远不要在if 中调用pthread_cond_wait,总是在while 循环中。

标签: pthreads mutex signals critical-section


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • pthread_cond_signal()/pthread_cond_broadcast() 表示:我已经完成了关键部分的工作,你,其他线程,可以拿起我为你准备的内容 -它可以是数据,也可以是写入新数据的空间,或任何其他类型的资源。那么,如果它们不能工作,为什么还要通知其他线程它们可以工作呢?
    【解决方案3】:

    在条件变量上等待的线程应该保持互斥锁锁定,而另一个线程应该始终在互斥锁被锁定的情况下发出信号。这样,当您发送信号时,您知道其他线程正在等待条件。否则,等待线程可能不会看到正在发出信号的条件,并且会无限期地阻塞等待。

    条件变量通常这样使用:

    #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 将一直等待——可能永远等待。这就是为什么要在互斥锁锁定的情况下发出信号。

    【讨论】:

    • "这是有效的" 请问是什么意思?至少在某种意义上不是避免可能的对共享变量的并发访问(go 这里)。
    • 即使在您的示例的第一个版本中,您将调用 pthread_cond_signal() 在受互斥锁 no 竞争保护的关键部分之外,并且 main()永远不会卡住。
    【解决方案4】:

    这里是关于条件变量的好文章:Techniques for Improving the Scalability of Applications Using POSIX Thread Condition Variables(查看“避免互斥争用”部分和第 7 点)

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

    【讨论】:

      【解决方案5】:

      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 完成之前醒来并销毁了条件变量。这是完全出乎意料的。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-17
        • 2013-03-26
        • 2013-04-07
        相关资源
        最近更新 更多