前言
博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产者消费者模型讲一讲,希望大家能看懂(没有现成和锁知识的朋友不要急,这部分是写给有基础的朋友看的,这些知识博主都会慢慢的讲到)。
什么是模型?
模型就是要解决某个问题的固定方法或套路,所以有时候我们学会一点模型是很有必要的。
生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
换一句话来说就是,比如说你在学校上课,好不容易熬到了放学,此时你肯定会和你的小伙伴一起去食堂吃饭(土豪除外,土豪不去食堂),但是因为之前食堂停水停电,导致,你们放学了才开始做饭,所以现在就会面临一个问题。
当食堂大叔做好饭之后会把饭放在指定的饭盒里面,但是食堂大叔做饭的速度可能比不上你和你小伙伴吃饭的速度(或者大叔做饭的速度太快导致们根本吃不过大叔的做饭速度)。这也就是我们的生产者消费者模型了。
所以博主先列出一些概念,更容易使大家理解:
生产者:产生数据的一方(食堂大叔做饭)
消费者:处理数据的一方(你和你的小伙伴吃饭)
我们会遇到的问题
双方处理数据的速度可能不同,这样就会导致总由一方吃处于阻塞状态。(也就是博主上面所说的吃饭的问题)可能博主的逻辑不是很严谨,但是将就着看吧。
要用到的函数解析:
int pthread_cond_wait
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 函数作用: 1.阻塞等待条件变量cond(参1)满足 2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex); 1.2.两步为一个原子操作。 3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex); 我们要知道的是在调用这个函数之前我们要做前准备好的事情: 1.mutex_t mutex 定义互斥量 2.init 初始化 3.lock 加锁 4.cond_t has_product 定义条件变量 5.inti 初始化
pthread_cond_signal
int pthread_cond_signal(pthread_cond_t *cond); 唤醒至少一个阻塞在条件变量上的线程
提到了这么多的东西了,接下来博主就给出生产者消费者模型的代码(里面有很详细的解释了,大家应该都能看懂,博主就不在做GIF动画了)
/*借助条件变量模拟 生产者-消费者 问题*/ #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <stdio.h> /*链表作为公享数据,需被互斥量保护*/ struct msg { struct msg *next; int num; }; struct msg *head; struct msg *mp; /* 静态初始化 一个条件变量 和 一个互斥量 完成 1 2 4 5 */ pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p) { while(true){ pthread_mutex_lock(&lock); //加锁 完成 3 while (head == NULL) { //头指针为空,说明没有节点 可以为if吗 pthread_cond_wait(&has_product, &lock); //此时会阻塞等待 等生产者传入之后才会继续工作 /* 1.阻塞等待条件变量cond(参1)满足 2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex); 注意: 1和2这两步为一个原子操作。 3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);*/ } mp = head; head = mp->next; //模拟消费掉一个产品 pthread_mutex_unlock(&lock); //解锁 printf("-Consume ---%d\n", mp->num); free(mp); //create 和 free 要成双成对出现 mp = NULL; sleep(rand() % 5); //随机随眠让 CUP 转移 } } void *producer(void *p) { while(true) { mp = malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; //模拟生产一个产品 printf("-Produce ---%d\n", mp->num); pthread_mutex_lock(&lock); //加锁(不用担心死锁,即使是消费者先调用,在pthread_cond_wait()之后也会将之前获得的锁给释放! //头插法 mp->next = head; head = mp; pthread_mutex_unlock(&lock); //解锁 pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程(生产者线程)唤醒 sleep(rand() % 5); //随机随眠让 CUP 转移 } } int main(int argc, char *argv[]) { pthread_t pid, cid; //pid-->生产者 cid -->消费者 srand(time(NULL)); /* 调用各自的主控函数 */ pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); //回收各自的线程! pthread_join(pid, NULL); pthread_join(cid, NULL); return 0; }
有朋友会遇到这种问题:
我为什么写的不能实现同步呢?就只是一个生产者在那干活
博主认为最有可能的原因是因为时序竞态,这种解决办法很简单加sleep就行了。
或者是 博主上面的两个函数没使用好导致这种情况,要记住 wait等的是生产的信号,signal发的是生产者的信号去唤醒阻塞的消费者。