生产者消费者模型
来源:互联网 发布:姚明对奥尼尔数据 编辑:程序博客网 时间: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()。
而条件变量的使用必须结合互斥锁的使用。否则对条件变量的通知可能发生在等待条件变量之前,从而造成死锁。导致死锁的最常见错误是自死锁或递归死锁。在自死锁或递归死锁中,线程尝试获取已被其持有的锁。
- 生产者-消费者模型
- 生产者/消费者模型
- 生产者 消费者模型
- 生产者消费者模型
- 生产者&&消费者模型
- 模拟生产者/消费者模型
- 生产者消费者模型
- 生产者消费者模型
- 生产者与消费者模型
- 生产者/消费者模型
- 生产者与消费者模型
- 生产者/消费者模型
- 生产者消费者模型
- 生产者消费者模型
- 生产者消费者模型
- 生产者消费者模型 ArrayBlockingQueue
- 生产者消费者模型
- 生产者消费者模型 - Storage
- 去百度面试运维开发
- PAT-A-1096. Consecutive Factors (20)
- (四)c++和JavaScript实现归并排序
- leetcode409. Longest Palindrome
- linux centos7 mysql无法远程连接解决办法
- 生产者消费者模型
- 322. Coin Change
- Arduino + AD9851 DDS信号模块 频率控制字和相位控制字写入
- pytorch 没有梯度
- 没事的时候,自己暗示自己
- python环境搭建-IDE
- 213.m1-至动画实现布局的显示与隐藏
- 解决oracle安装在linux中jdk的冲突
- C++返回字符串函数的四种实现方法