深入理解Linux 条件变量4:pthread_cond_signal惊群现象及解决方法(while判断)

前言

我们先解释一下什么叫惊群, 所谓惊群就是当一个条件或事件触发时,所有等待这个条件或事件的线程或进程都被唤醒了。打个比方,你要去猪圈喂猪,每次你就扔一个馒头,一群猪都会被唤醒,然后去抢馒头,但一次只有一个馒头,只够一头猪吃一口,别的猪,被唤醒了也没用。这就叫惊群,惊动了一群猪。希望能帮助大家理解,呵呵。

pthread_cond_signal惊群

在前面的文章《深入理解Linux 条件变量1:使用场景、接口说明
中我们介绍pthread_cond_signal和pthead_cond_broadcast时提到:

  1. pthread_cond_signal :通知条件变量状态变化,能够唤醒至少1个等待该条件的线程。
  2. pthread_cond_broadcast: 广播通知条件变量状态变化,唤醒所有等待该条件的线程。

注意上面的说法,pthread_cond_signal是 唤醒至少 1个,并不是有且只有1个,这可能是因为内部实现过于复杂,所以该接口现状就如此。那么到底会不会出现一次唤醒了2个线程呢?我们还是通过一个示例代码来验证一下,demo代码如下:

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

pthread_mutex_t count_lock;
pthread_cond_t count_ready;
int count;

void *thread_consumer(void *arg)
{
        char *name = (char *)arg;
        for(;;){
                pthread_mutex_lock(&count_lock);
                printf("[%s]decrement:waiting\n", name);
                /*等待满足条件,期间互斥量仍然可用*/
                if(count == 0){
                        pthread_cond_wait(&count_ready, &count_lock);
                }
                printf("[%s]decrement:count = %d\n",  name, count);
                if (count == 0)
                {
                        printf("[%s]exit count:%d\n", name, count);
                        exit(1);
                }
                count = 0;
                pthread_mutex_unlock(&count_lock);
        }
        pthread_exit(NULL);
}
void *thread_producer(void *arg)
{
        sleep(1);
        while(1){
                pthread_mutex_lock(&count_lock);
                count = 1;
                pthread_mutex_unlock(&count_lock);
                pthread_cond_signal(&count_ready);
        }
        pthread_exit(NULL);
}

int main(void)
{
        pthread_t tid1,tid2,tid3;

        const char *tid_name1 = "th_consumer1";
        const char *tid_name2 = "th_producer";
        const char *tid_name3 = "th_consumer2";

        count=0;
        pthread_mutex_init(&count_lock, NULL);
        pthread_cond_init(&count_ready, NULL);

        pthread_create(&tid2, NULL, thread_producer, (void *)tid_name2);
        pthread_create(&tid1, NULL, thread_consumer, (void *)tid_name1);
        pthread_create(&tid3, NULL, thread_consumer, (void *)tid_name3);
        
        /*等待decrement退出*/
        pthread_join(tid2, NULL);
        printf("thread_consumer quit\n");
        pthread_join(tid3, NULL);
        pthread_join(tid1, NULL);
        return 0;
}

在上面的示例代码中,在thread_consumer消费者线程里,如果出现了惊群现象,也就是同时唤醒了2个消费线程,则程序会退出,如果不出现惊群现象,程序会正常节奏运行。运行结果如下:

ubuntu@VM-0-17-ubuntu:/opt/test/cond-test$ ./test 
[th_consumer1]decrement:waiting
[th_consumer2]decrement:waiting
[th_consumer2]decrement:count = 1
[th_consumer2]decrement:waiting
[th_consumer2]decrement:count = 1
[th_consumer2]decrement:waiting
[th_consumer1]decrement:count = 1
[th_consumer1]decrement:waiting
[th_consumer2]decrement:count = 0
[th_consumer2]exit count:0

果然,很快就出现了惊群现象。

条件变量惊群现象解决方法

我们还是拿前面喂猪的例子来分析,如果这群猪都比较聪明,每次被唤醒后,先检查一下嘴巴跟前有没有多余的馒头,如果有就起来吃,没有就继续睡觉等待,是不是就避免了猪乱抢的结果。我们按照这个思路来修改一下上面消费线程的实现,如下所示:

void *thread_consumer(void *arg)
{
        char *name = (char *)arg;
        for(;;){
                pthread_mutex_lock(&count_lock);
                printf("[%s]decrement:waiting\n", name);
                /*等待满足条件,期间互斥量仍然可用*/
                while(count == 0){
                        pthread_cond_wait(&count_ready, &count_lock);
                }
                printf("[%s]decrement:count = %d\n",  name, count);
                if (count == 0)
                {
                        printf("[%s]exit count:%d\n", name, count);
                        exit(1);
                }
                count = 0;
                pthread_mutex_unlock(&count_lock);
        }
        pthread_exit(NULL);
}

我们使用while循环来检查条件,当线程醒来后,因为还在while循环内,所以还会在判断count是否为0,如果为0,就返回继续等待,这样就能解决pthread_cond_signal的惊群现象了。

小结

pthread_cond_signal不能保证每次只唤醒1个等待线程,可能会唤醒多个,所以会导致惊群现象,为了解决惊群现象,我们可以在pthread_cond_wait前面 通过while 判断 条件变量标识的队列或变量的状态是否可用,只有可用才去消费,如果不可用,则继续返回等待。

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
pthread_cond_wait和pthread_cond_signal是用于线程间同步的函数,通常配合使用。 pthread_cond_wait用于等待条件变量的信号。当一个线程调用pthread_cond_wait时,它阻塞等待,直到另一个线程调用pthread_cond_signal或pthread_cond_broadcast来发送信号。 pthread_cond_signal用于发送条件变量的信号。当一个线程调用pthread_cond_signal时,它唤醒一个正在等待这个条件变量的线程。如果没有线程在等待,则信号被忽略。 一般情况下,pthread_cond_wait和pthread_cond_signal是成对出现的。一个线程等待某个条件满足时(例如等待某个资源可用),调用pthread_cond_wait进入等待状态。另一个线程在某个条件满足时,调用pthread_cond_signal发送信号,唤醒正在等待的线程。 需要注意的是,pthread_cond_wait函数在接收到信号后,还需要重新检查条件是否满足,因为可能存在虚假唤醒的情况。 以下是一个简单的示例代码: ```c pthread_mutex_t mutex; pthread_cond_t cond; int condition = 0; void* thread1(void* arg) { pthread_mutex_lock(&mutex); while (condition == 0) { pthread_cond_wait(&cond, &mutex); } // 条件满足后执行的代码 pthread_mutex_unlock(&mutex); return NULL; } void* thread2(void* arg) { pthread_mutex_lock(&mutex); condition = 1; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t tid1, tid2; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); pthread_create(&tid1, NULL, thread1, NULL); pthread_create(&tid2, NULL, thread2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } ``` 在上述示例中,thread1线程调用pthread_cond_wait等待条件满足,而thread2线程在某个时刻将条件设置为满足,并调用pthread_cond_signal发送信号。这样,thread1线程被唤醒后执行相应的代码。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值