【问题标题】:Why isn't pthread_cond_signal() being called?为什么不调用 pthread_cond_signal()?
【发布时间】:2022-09-29 23:25:28
【问题描述】:

所以我试图理解pthread_cond_t 变量,但问题往往是有时pthread_cond_signal()/pthread_cond_broadcast() 不起作用并且睡眠线程没有被唤醒,导致我的代码出现死锁。 代码有问题吗?使用条件变量的更好/最佳方法是什么?

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t lock;
pthread_cond_t cv;
int count = 0;
void* routine(){
    pthread_mutex_lock(&lock);
    while(count!=5) pthread_cond_wait(&cv,&lock);
    printf(\"count value is : %d\\n\", count);
    pthread_mutex_unlock(&lock);
}

void* routine2(){
    pthread_mutex_lock(&lock);
    for(int i=0; i<7; i++){
        count++;
        printf(\"%d\\n\",count);
        if(count == 5) pthread_cond_signal(&cv);
    }
    pthread_mutex_unlock(&lock);
}
int main(){

    pthread_mutex_init(&lock,NULL);
    pthread_cond_init(&cv,NULL);
    pthread_t t1,t2;
    pthread_create(&t1,NULL,&routine,NULL);
    pthread_create(&t2,NULL,&routine2,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cv);
}
  • 你期望这段代码做什么?它有什么作用?
  • routine2 是否在 count 为 5 时释放互斥锁?

标签: c multithreading pthreads mutex conditional-variable


【解决方案1】:

问题是您的逻辑基于变量,这些变量可以在您读取变量的时间和测试值的时间之间发生变化。

在这段代码中:

    while(count!=5) pthread_cond_wait(&cv,&lock);

如果首先调用routine(),这隐含地意味着count 为0(因为routine2() 是唯一改变count 的函数,并且没有锁)。 routine() 将调用 pthread_cond_wait,这将释放互斥锁,然后阻塞,直到发出条件信号。请注意,它还必须能够在完成之前获得互斥锁(即仅信号是不够的)。来自pthread_cond_wait

成功返回后,互斥锁已被锁定并归 调用线程

如果routine2() 首先获得锁,它将迭代直到count 为5,然后调用pthread_cond_signal。但是,这不会让routine() 继续,因为锁不会同时释放。它将继续遍历循环并立即将count 增加到 6,routine() 曾经有机会从 count 变量中读取。由于countroutine() 恢复时不可能是5,所以会死锁(永远卡在上面的行中)。

您可以通过简单地不使用routine2() 获得锁来避免这种情况:

void* routine2(){
    for(int i=0; i<7; i++){
        count++;
        printf("%d\n",count);
        if(count == 5) {
            pthread_cond_signal(&cv);
        }
    }
}

这也有问题,因为不能保证count 会在读取routine() 时为5,因为没有什么可以阻止routine2() 继续处理。如果发生这种情况,routine() 将再次死锁,因为计数将增加到 5 以上。

以下更改通过最少的更改解决了该问题。一个主要的变化是仅在count 小于 5 时才等待(如果它是 5 或更多,则信号已经发送)。

2)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t lock;
pthread_cond_t cv;
int count = 0;
void* routine(){
    pthread_mutex_lock(&lock);
    if (count < 5) {
        pthread_cond_wait(&cv,&lock);
        printf("routine: got signal (%d)\n", count);
    } else {
        printf("routine: signal already sent\n");
    }
    pthread_mutex_unlock(&lock);
}

void* routine2(){
    pthread_mutex_lock(&lock);
    for(int i=0; i<7; i++){
        count++;
        printf("%d\n",count);
        if(count == 5) pthread_cond_signal(&cv);
    }
    pthread_mutex_unlock(&lock);
}

int main(){
    pthread_mutex_init(&lock,NULL);
    pthread_cond_init(&cv,NULL);
    pthread_t t1,t2;
    pthread_create(&t1,NULL,&routine,NULL);
    pthread_create(&t2,NULL,&routine2,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cv);
} 

这个OnlineGDB snippet 展示了死锁的可能性。

【讨论】:

  • 他们不能通过避免锁定routine2() 来解决问题。这将产生涉及变量count 的数据竞争,从而产生未定义的行为。生成的 UB 不太可能以与程序当前行为相同的方式表现出来。由于您还注意到其他问题,因此尚不清楚您为什么要提出它。
【解决方案2】:

您正在正确使用条件变量和互斥锁,但算法失败。这里有两个条件:

  1. t1必须唤醒一次数数等于 5;
  2. 何时数数等于 5,在此之前不得更改t1被唤醒。

    您的算法缺少第二个条件t2.为此,请设置一个名为t1_woken_up表示t1已被唤醒以显示柜台等于 5。

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <pthread.h>
    #include <unistd.h>
    
    pthread_mutex_t lock;
    pthread_cond_t cv;
    int count = 0;
    int t1_woken_up = 0;
    
    void* routine(){
        pthread_mutex_lock(&lock);
        while(count!=5) pthread_cond_wait(&cv,&lock);
        printf("t1: count value is %d\n", count);
        printf("t1: Waking up t2\n");
        t1_woken_up = 1;
        pthread_cond_signal(&cv);
        pthread_mutex_unlock(&lock);
    }
    
    void* routine2(){
        pthread_mutex_lock(&lock);
        for(int i=0; i<7; i++){
            count++;
            printf("t2: %d\n",count);
            if(count == 5) {
              printf("t2: Waking up t1\n");
              pthread_cond_signal(&cv);
              while (!t1_woken_up) {
                pthread_cond_wait(&cv,&lock);
              }
            }
        }
        pthread_mutex_unlock(&lock);
    }
    
    int main(){
    
        pthread_mutex_init(&lock,NULL);
        pthread_cond_init(&cv,NULL);
        pthread_t t1,t2;
        pthread_create(&t1,NULL,&routine,NULL);
        pthread_create(&t2,NULL,&routine2,NULL);
    
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
    
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&cv);
    }
    

    执行示例:

    $ gcc t.c -o t -lpthread
    $ ./t
    t2: 1
    t2: 2
    t2: 3
    t2: 4
    t2: 5
    t2: Waking up t1
    t1: count value is 5
    t1: Waking up t2
    t2: 6
    t2: 7
    

    注意:根据this post 和底层 cmets,没有必要(最好说“不建议”)为共享变量(即数数t1_woken_up变量)。

【讨论】:

  • 参考Using C/Pthreads: do shared variables need to be volatile?(回答:.) 从答案之一:“volatile 的使用既不必要也不足够。这不是必需的,因为适当的同步原语就足够了。这还不够,因为它只会禁用一些优化,而不是所有可能会咬人的优化你。”
  • 感谢您提供@JohnBollinger 的信息。但是阅读链接并不是很清楚编译器如何知道在这种情况下没有应用优化。我同意 volatile 注定不会同步这一事实,但我们必须保证读取它的函数在获得锁后读取它时将获得变量的最新版本。如果它不是易失性的,编译器可能会优化它吗?
  • 提供一致 pthreads 支持的 C 实现必须避免在语义正确的 pthreads 程序中执行不适当的优化,并且 pthreads 不要求共享变量为volatile 以保证语义正确性。编译器如何处理它是它的关注点,但这确实强调了 pthreads 不仅仅是一个库的事实。您可能需要给编译器一个命令行选项,例如 GCC 的-pthread,但您不需要声明共享变量volatile,至少不是因为这个原因。
  • 好的,我会相应地更新帖子。
猜你喜欢
  • 2018-03-27
  • 2016-02-23
  • 2022-08-16
  • 1970-01-01
  • 2015-03-09
  • 2011-07-29
  • 1970-01-01
  • 2012-01-11
  • 2016-07-15
相关资源
最近更新 更多