Linux下生产者消费者模型--及pthread_cond_signal出现的问题

首先我们要了解生产者消费者模型的运行机制,生产者线程用来收集用户数据,数据保存在共享内存区,消费者线程需要从共享内存中取出数据进行处理。

下面基于我们的代码,做一个简单的说明,为了简单化,生产者线程不直接去收集我们用户的数据,而是直接产生数据,我们让生产者从0开始一直生产自然数。消费者对数据的处理也仅仅是将自然数打印出来。共享内存我们使用链表,因为链表我们不用但是满的情况。下面是具体实现的代码(代码实现的是一个生产者和一个消费者)

代码:

​
#include <stdlib.h>                                                                                                                                                                                  
#include <sys/wait.h>
#include <sys/socket.h>
#include <pthread.h>
//生产者消费者模型,共享空间使用链表,不用担心链表满的情况。使用互斥锁
struct student
{   
    int nu;
    struct student* next;
};
struct student *head;//定义链表的头节点
//定义一个互斥锁
pthread_mutex_t lock;
//定义一个条件变量
pthread_cond_t cond;

//生产者
void * producer(void * arg)
{   
    int i=0;
    while(1)
    {
        struct student* node=(struct student*)malloc(sizeof(struct student*));
        node->nu=i++;
        printf("+++++生产者生产数据 i=%d\n",node->nu);
        //要把数据写道链表中,采用头插法。需要加锁
        pthread_mutex_lock(&lock);
        node->next=head;
        head=node;
        pthread_mutex_unlock(&lock);//解锁
        //生产者写了数据,head不为空,这是生产者肯定的事情,所以要在生产者里唤醒
        //被条件变量阻塞的消费者
        pthread_cond_signal(&cond);
        usleep(50000);//避免打印太快看不出效果
     }
     return NULL;
}

//消费者
void* cosumer(void* arg)
{
    struct student* node;
    if(1)
    {
        pthread_mutex_lock(&lock);
        while(head==NULL)
        {
             //head为空,说明生产者没有生产。阻塞。使用条件变量解决
             pthread_cond_wait(&cond,&lock);
        }
        //摘取头节点
        node=head;
        head=head->next;
        pthread_mutex_unlock(&lock);//解锁
        printf("--------消费者处理数据 i=%d\n",node->nu);
        free(node);//避免内存泄漏
        usleep(50000);//避免打印太快看不出效果
    }
    return NULL;
}

//主线程什么都不做,只是定义
int main(int argc, char* argv[])
{
    pthread_mutex_init(&lock,NULL);//初始化互斥锁
    pthread_t p_tid;
    pthread_t c_tid;
    pthread_create(&p_tid,NULL,producer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
   
    while(1);//线程是依托进程的,我们不让进程结束
    pthread_mutex_destroy(&lock);
    pthread_exit(NULL);
}

​

运行结果:

 问题:在上面代码的基础上,如果我们增加多个消费者,而且生产者不变依然只有一个,会怎么样呢??

我们稍微改一改代码:把消费者变成5个,其他不变


//主线程什么都不做,只是定义
int main(int argc, char* argv[])
{
    pthread_mutex_init(&lock,NULL);//初始化互斥锁
    pthread_t p_tid;
    pthread_t c_tid;
    pthread_create(&p_tid,NULL,producer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
    pthread_create(&c_tid,NULL,cosumer,NULL);
    while(1);//线程是依托进程的,我们不让进程结束
    pthread_mutex_destroy(&lock);
    pthread_exit(NULL);
}

​

​

执行结果:

错误:很明显发生了段错误,段错误不一定发生,但是一直运行迟早会发生,这是为什么为什么呢????

分析原因:首先分析我们的代码为什么会出现段错误,可能会在那个地方出现。段错误(无效的地址访问),从我们代码可以看出当head=NULL时,执行了head=head->next。即对空节点访问了next。才可能出现段错误。

解释:首先假设执行到摸一个阶段当前链表head为空,五个消费者线程(不一定是五个,至少两个)都执行了,发现head为空,则阻塞在条件变量里面。这时候生产者写了一个数据,链表里面只有一个节点不为空。这时pthread_cond_signal唤醒消费者。问题就出在pthread_cond_signal函数上面。官方给的解释,包括man里面的解释也是,唤醒一个阻塞在条件变量上的线程,但是实际上它唤醒了一个或者多个。其实这是一个BUG。假设有两个消费者(A和B)都被pthread_cond_signal唤醒了。此时,A和B取消阻塞进行加锁,但是A/B中的一个能加锁成功,假设是A,则B阻塞在加锁上,A读走了唯一的数据,现在head=NULL。当A开锁的一瞬间,B加锁成功,执行读取操作,但是head=NULL。所以执行代码head=head->next是发生段错误。如果真的pthread_cond_signal只是唤醒了一个线程,就不会发生这种情况,事实证明pthread_cond_signal函数是唤醒一个或者多个线程,而不是只唤醒一个。

如何解决:把消费者中的  if(head==NULL)改成 while(head==NULL)。改成while后,线程B在A开锁的一瞬间加锁成功之后它会继续判断head==NULL的条件语句,而不是直接运行下面的代码。所以以后不管使用pthread_cond_signal还是pthread_cond_broadcast()最好都用while。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值