生产者消费者模型

我们来认识一下一个模型:生产者消费者模型。在实际的软件开发过程中,可能遇到这种情况:一个模块负责产生数据,另一个模块负责处理数据。注:这里的模块是广义上的模块,可能是进程,线程,类等。产生数据的模块是生产者,处理数据的模块是消费者。但是还不够得上生产者消费者模型,这里还需要一个缓冲区,处于生产者消费者中间,作为中介,生产者往缓冲区生产数据,消费者从缓冲区消费数据。

缓冲区的优势:
解耦:假设生产者,消费者是两个类,如果生产者直接调用消费者,那么生产者会对消费者产生依赖关系,如果消费者代码出现变化,则很可能会影响到生产者。而它俩中间都依赖缓冲区,两者之间不直接解除,耦合也就降低了。
支持并发:由于函数是同步的,在消费者的方法还没有返回之前,生产者只能等待,消费者的效率直接影响到了生产者的生产。而支持并发就是它俩是独立的并发主体,生产者生产数据后往缓冲区一丢,就继续生产,基本上不依赖消费者的处理速度。
支持忙闲不均:如果生产者生产速度时快时慢,缓冲区优势就凸显出来了:快时消费者来不及处理,就丢到缓冲区,等生产速度降下来时,再慢慢处理。

我们来实现基于单链表的生产者消费者模型,我们先看下面代码,模拟生产消费过程

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
typedef struct _node
{
    int data;
    struct _node* next;
    }node_t,*node_p,**node_pp;

node_p head = NULL;   //定义头结点

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;   //定义锁

static node_p alloc_node(int _d)
{
    node_p tmp = (node_p)malloc(sizeof(node_t));
    if(!tmp)
    {
        perror("malloc");
        exit(1);
    }
        tmp->data = _d;
        tmp->next = NULL;
    }

static void delete_node(node_p _n)
{
    if(_n != NULL)
    {
        free(_n);
    }
}

static int isEmpty(node_p _h)
{
    return (NULL == _h->next) ? 1:0;
}

void initList(node_pp _h)
{
    *_h = alloc_node(0);
    }

void pushFront(node_p _h,int _d)
{
    node_p tmp = alloc_node(_d);
    tmp->next = head->next;
    head->next = tmp;
    }


void popFront(node_p _h,int* _out)
{
    if(!isEmpty(_h))
    {
        node_p p = _h->next;
        _h->next = p->next;
        *_out = p->data;
        delete_node(p);
    }
}

void destroyList(node_p _h)
{
    int out = 0;
    while(!isEmpty(_h))
    {
        popFront(_h,&out);
        }
    delete_node(_h);
    }

void showList(node_p _h)
{
    node_p start = _h->next;
    while(start)
    {
        printf("%d ",start->data);
        start = start->next;
        }
    printf("\n");
    }

void* product(void* arg)     //生产者
{
    while(1)
    {
        int p =  rand()%1234;
        pthread_mutex_lock(&lock);  //加锁
        pushFront(head,p);    //临界区
        pthread_mutex_unlock(&lock);   //解锁
        printf("product done:%d\n",p);
        sleep(1);
        }
    }

void* consume(void* arg)    //消费者
{
    int c;
    while(1){
        c = -1;
        pthread_mutex_lock(&lock);
        while(!isEmpty(head)){
                popFront(head,&c);  
            }
            pthread_mutex_unlock(&lock);
            printf("consume done:%d\n",c);
        }
    }

int main()
{
    initList(&head); 

    int i = 0;
    for(;i<10;i++){
        pushFront(head,i);
        showList(head);
        sleep(1);
        }

    int out = 0;
    for(;i>5;i--){
        popFront(head,&out);
        showList(head);
        sleep(1);
        }

        destroyList(head);
        return 0;
    }

我们来看运行结果:
这里写图片描述

由于生产者生产数据之后sleep一秒,这时就造成一种持续访问的现象,消费者就会一直访问资源(链表),导致生产者长时间无法访问资源,这种情况我们应该是要规避的。所以我们就需要消费者在资源中无数据时等待,等生产者产生数据后再通知他进行访问,所以引入了一个新的概念:条件变量条件变量使线程可以睡眠等待一个条件的出现。它是利用线程间共享的全局变量来进行同步的一种机制,主要有两个动作:一个线程等待“条件变量的条件成立”而等待;另一线程使“条件成立”(给出条件成立信号)。为防止竞争,条件变量的使用总是和互斥锁结合在一起使用。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
typedef struct _node
{
    int data;
    struct _node* next;
    }node_t,*node_p,**node_pp;

node_p head = NULL;   //定义头结点

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;   //定义锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;    //定义条件变量

static node_p alloc_node(int _d)
{
    node_p tmp = (node_p)malloc(sizeof(node_t));
    if(!tmp)
    {
        perror("malloc");
        exit(1);
    }
        tmp->data = _d;
        tmp->next = NULL;
    }

static void delete_node(node_p _n)
{
    if(_n != NULL)
    {
        free(_n);
    }
}

static int isEmpty(node_p _h)
{
    return (NULL == _h->next) ? 1:0;
}

void initList(node_pp _h)
{
    *_h = alloc_node(0);
    }

void pushFront(node_p _h,int _d)
{
    node_p tmp = alloc_node(_d);
    tmp->next = head->next;
    head->next = tmp;
    }


void popFront(node_p _h,int* _out)
{
    if(!isEmpty(_h))
    {
        node_p p = _h->next;
        _h->next = p->next;
        *_out = p->data;
        delete_node(p);
    }
}

void destroyList(node_p _h)
{
    int out = 0;
    while(!isEmpty(_h))
    {
        popFront(_h,&out);
        }
    delete_node(_h);
    }

void showList(node_p _h)
{
    node_p start = _h->next;
    while(start)
    {
        printf("%d ",start->data);
        start = start->next;
        }
    printf("\n");
    }

void* product(void* arg)     //生产者
{
    while(1)
    {
        int p =  rand()%1234;
        pthread_mutex_lock(&lock);  //加锁
        pushFront(head,p);    //临界区
        pthread_mutex_unlock(&lock);   //解锁
        pthread_cond_signal(&cond);    //发送一个信号给另外一个正处于阻塞等待状态的线程(本例中的消费者),使其脱离阻塞状态,继续执行,如果没有线程处于阻塞状态,也会成功返回。
        printf("product done:%d\n",p);
        sleep(1);
        }
    }

void* consume(void* arg)    //消费者
{
    int c;
    while(1){
        c = -1;
        pthread_mutex_lock(&lock);
        while(isEmpty(head)){
            printf("consume begin waiting\n");
            pthread_cond_wait(&cond,&lock);   //消费失败,在cond等待,并且释放锁lock,等再次被唤醒时,重新获得锁
            }
            popFront(head,&c);   
            pthread_mutex_unlock(&lock);
            printf("consume done:%d\n",c);
        }
    }

int main()
{
    initList(&head); 
    pthread_t p,c;   
    pthread_create(&p,NULL,product,NULL);
    pthread_create(&c,NULL,consume,NULL);
    pthread_join(p,NULL);   //线程默认是可结合的,所以线程等待
    pthread_join(c,NULL);
    pthread_mutex_destroy(&lock);   
    pthread_cond_destroy(&cond);
    return 0;
}

这里写图片描述

定义两个条件变量,使其互相通信

void* product(void* arg)
{
    while(1)
    {
        int p =  rand()%1234;
        pthread_mutex_lock(&lock);
        while(!isEmpty(head)){
            printf("product begin waiting\n");
            pthread_cond_wait(&cond2,&lock);
            }
        pushFront(head,p);
        pthread_mutex_unlock(&lock);
        pthread_cond_signal(&cond);   //p->c
        printf("product done:%d\n",p);
        //sleep(1);
        }
    }

void* consume(void* arg)
{
    int c;
    while(1){
        c = -1;
        pthread_mutex_lock(&lock);
        while(isEmpty(head)){
            printf("coonsume begin waiting\n");
            pthread_cond_wait(&cond,&lock);
            }
            popFront(head,&c);
            pthread_cond_signal(&cond2);  //c->p
            pthread_mutex_unlock(&lock);
            printf("consume done:%d\n",c);
            sleep(1);
        }
    }

这里写图片描述

基于环形队列的生产者消费者模型

基于环形队列的模型类似进程间通信使用信号量的方法,生产者生产数据时,需要P操作(申请格子资源),释放的时候即生产数据之后V操作。而消费者拿数据时进行P操作(申请数据资源),释放的时候即拿走数据之后V操作。

这里必须满足下面条件:
1,消费者一直在生产者后边;
2,生产者不能给消费者套圈;
3,两者不能同时出现在一个格子中,除非队列满了或队列为空。满时,消费者先走,空时,生产者先走。

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>

#define SIZE 64

int ring[SIZE];
sem_t blank_sem;
sem_t data_sem;

void *product(void *arg)
{
    int step = 0;
    int data = 0;
    while(1)
    {
        sem_wait(&blank_sem);
        ring[step++] = data;
        sem_post(&data_sem);
        step %= SIZE;
        printf("Product done:%d\n",data++);
        sleep(1);
    }
}

void *consum(void *arg)
{
    int step = 0;
    while(1)
    {
        sleep(1);
        sem_wait(&data_sem);
        int data = ring[step++];
        sem_post(&blank_sem);
        step %= SIZE;
        printf("comsum done:%d\n",data);
    }

}

int main()
{
    sem_init(&blank_sem,0,SIZE);
    sem_init(&data_sem,0,0);

    pthread_t p,c;
    pthread_create(&p,NULL,product,NULL);
    pthread_create(&c,NULL,consum,NULL);
    pthread_join(p,NULL);
    pthread_join(c,NULL);

    sem_destroy(&blank_sem);
    sem_destroy(&data_sem);

    return 0;
}

这里写图片描述

改成多生产者多消费者模型

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>

#define SIZE 64

int ring[SIZE];
sem_t blank_sem;
sem_t data_sem;

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;


void *product1(void *arg)
{
    int step = 0;
    int data = 0;
    while(1)
    {
        pthread_mutex_lock(&lock1);
        sem_wait(&blank_sem);
        ring[step++] = data;
        sem_post(&data_sem);
        pthread_mutex_unlock(&lock1);
        step %= SIZE;
        printf("product1 done:%d\n",data++);
        sleep(1);
    }
}

void *product2(void *arg)
{
    int step = 0;
    int data = 0;
    while(1)
    {
        pthread_mutex_lock(&lock1);
        sem_wait(&blank_sem);
        ring[step++] = data;
        sem_post(&data_sem);
        pthread_mutex_unlock(&lock1);
        step %= SIZE;
        printf("product2 done:%d\n",data++);
        sleep(1);
    }

}

void *consum1(void *arg)
{
    int step = 0;
    while(1)
    {
        sleep(1);
        pthread_mutex_lock(&lock2);
        sem_wait(&data_sem);
        int data = ring[step++];
        sem_post(&blank_sem);
        pthread_mutex_unlock(&lock2);
        step %= SIZE;
        printf("consum1 done:%d\n",data);
    }

}

void* consum2(void* arg)
{
    int step = 0;
    while(1)
    {
        sleep(1);
        pthread_mutex_lock(&lock2);
        sem_wait(&data_sem);
        int data = ring[step++];
        sem_post(&blank_sem);
        pthread_mutex_unlock(&lock2);

        step %= SIZE;
        printf("consum2 done:%d\n",data);
    }
}

int main()
{
    sem_init(&blank_sem,0,SIZE);
    sem_init(&data_sem,0,0);

    pthread_t p1,p2,c1,c2;
    pthread_create(&p1,NULL,product1,NULL);
    pthread_create(&p2,NULL,product2,NULL);
    pthread_create(&c1,NULL,consum1,NULL);
    pthread_create(&c2,NULL,consum2,NULL);
    pthread_join(p1,NULL);
    pthread_join(p2,NULL);
    pthread_join(c1,NULL);
    pthread_join(c2,NULL);

    sem_destroy(&blank_sem);
    sem_destroy(&data_sem);

    return 0;
}

注:
调用pthread_cond_wait()之前必须获得锁。它是对互斥量做了操作,首先将解锁和挂起线程作为原子操作执行,pthread_cond_signal()即可获得互斥量。当pthread_cond_wait()返回时,又会自动加锁,所以其效果相当于持有的锁的状态未发生改变。
而一个阻塞线程被唤醒可能是被pthread_cond_signal()/pthread_cond_broadcast()唤醒,也可能是在被信号中断后唤醒(造成假唤醒)。所以pthread_cond_wait()的返回并不意味着条件的值发生了改变(假唤醒),也可能是函数出错返回,因此必须注意因此必须重新检查条件的值,比如使用while()。
而条件变量的使用必须结合互斥锁的使用。否则对条件变量的通知可能发生在等待条件变量之前,从而造成死锁。导致死锁的最常见错误是自死锁或递归死锁。在自死锁或递归死锁中,线程尝试获取已被其持有的锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值