生产者消费者模型

来源:互联网 发布:姚明对奥尼尔数据 编辑:程序博客网 时间:2024/06/08 19:08

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

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

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

#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 64int 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 64int 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()。
而条件变量的使用必须结合互斥锁的使用。否则对条件变量的通知可能发生在等待条件变量之前,从而造成死锁。导致死锁的最常见错误是自死锁或递归死锁。在自死锁或递归死锁中,线程尝试获取已被其持有的锁。

原创粉丝点击