生产者消费者模型
1.什么是生产者消费者问题?
场景:模块A产生数据,这些数据由模块B来处理。
形象的称模块A为生产者,模块B为消费者。
生产者消费者问题也称有限缓冲问题,是多线程同步问题的一个经典案例。描述了两个共享固定大小缓冲区的线程,即生产者和消费者。生产者负责生产数据放入缓冲区中,消费者消费缓冲区中的数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
2.为什么要使用生产者消费者模型?
1.解耦:如果让生产者直接调用消费者的某个方法,那么生产者就会对消费者产生依赖(耦合),“消费者”代码发生变化,可能会影响“生产者”(调用方式、参数类型...)。生产者消费者模型通过一个缓冲区来解决生产者和消费者之间的强耦合问题。生产者和消费者之间不直接通讯,而是通过缓冲区进行数据传递,平衡了生产者和消费者的处理能力。降低了依赖/耦合。
2.支持并发:让生产者直接调用消费者的某个方法,由于函数调用时同步的,即在消费者的函数没有返回前,生产者只能阻塞式的等着。如果消费者处理数据很慢,这样显然是很低效的。在生产者消费者模型中,生产者和消费者可以是两个独立的并发主体,生产者只需生产数据放入缓冲区后,接着去生产下一个数据,不用等待消费者消费完成。
3.支持忙闲不均:如果生产者生产数据时快时慢,在生产者生产较快时,消费者消费速度有限,不能及时消费数据,可将数据先寄放在缓冲区中,因为生产者生产速度会降下来,因此消费者可随后逐渐消费完缓冲区中的数据。
3.模拟生产者消费者模型
1.基于阻塞队列的生产者消费者模型:
与普通队列的区别在于,当队列为空时,pop操作将被阻塞,直到队列中被放入了元素;当队列为满时,push操作将被阻塞,直到有元素被取走。(以上pop和push操作分别由不同线程来完成)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//cp.hpp #ifndef __PC_HPP_ #define __PC_HPP_ #include <iostream> #include <pthread.h> #include <queue> using namespace std; #define NUM 8 class BlockQueue { private: queue<int> q; size_t cap; //队列容量 pthread_mutex_t mutex; pthread_cond_t full; pthread_cond_t empty; private: void LockQueue() { pthread_mutex_lock(&mutex); } void UnLockQueue() { pthread_mutex_unlock(&mutex); } void ProducterWait() { pthread_cond_wait(&full, &mutex); //队列满时阻塞 } void NoticeProducter() { pthread_cond_signal(&full); //唤醒被条件变量full阻塞的线程 } void ConsumerWait() { pthread_cond_wait(&empty, &mutex); //队列空时阻塞 } void NoticeConsumer() { pthread_cond_signal(&empty); //唤醒被条件变量empty阻塞的线程 } bool IsFull() { return q.size() == cap; } bool IsEmpty() { return q.size() == 0; } public: BlockQueue(size_t _cap = NUM) :cap(_cap) { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&full, NULL); pthread_cond_init(&empty, NULL); } ~BlockQueue() { pthread_mutex_destroy(&mutex); pthread_cond_destroy(&full); pthread_cond_destroy(&empty); } void PushData(const int& val) { LockQueue(); while(IsFull()) { NoticeConsumer(); //满,通知消费者消费 cout << "Queue is full, producter block, consume data." << endl; ProducterWait(); //生产者被阻塞 } q.push(val); // NoticeConsumer(); //生产一个通知消费者消费 UnLockQueue(); } void PopData(int& val) { LockQueue(); while(IsEmpty()) { NoticeProducter(); //空,通知生产者生产 cout << "Queue is empty, consumer block, product data." << endl; ConsumerWait(); } val = q.front(); q.pop(); NoticeProducter(); //消费一个通知生产者生产 UnLockQueue(); } }; #endif
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//cp.cc #include "pc.hpp" #include <ctime> #include <unistd.h> #include <cstdlib> void *Producter(void *arg) { BlockQueue *bq = (BlockQueue *)arg; while(1) { int data = rand() % 100 + 1; bq->PushData(data); cout << "Product data: " << data << endl; // sleep(1); //1秒生产一个 } return (void *)0; } void *Consumer(void *arg) { BlockQueue *bq = (BlockQueue *)arg; int data; while(1) { bq->PopData(data); cout << "Consume data: " << data << endl; sleep(1); //1秒消费一个 } return (void *)0; } int main() { srand((unsigned int)time(NULL)); pthread_t c, p; BlockQueue bq; pthread_create(&p, NULL, Producter, (void *)&bq); pthread_create(&c, NULL, Consumer, (void *)&bq); pthread_join(p, NULL); pthread_join(c, NULL); return 0; }
2.基于环形队列的生产者消费者模型:
用数组来模拟环形队列,采用POSIX信号量来实现生产者线程与消费者线程的同步行为。信号量data_sem表示生产者生产的数据个数,信号量space_sem表示缓冲区中的空位。会出现以下四种情况:
- 当data_sem值大于0时,消费者可进行消费;
- 当space_sem值大于0时,生产者可进行生产;
- 当data_sem=0,即space_sem=队列大小时,消费者被阻塞;
- 当space_sem=0,即data_sem=队列大小时,生产者被阻塞。
生产者每生产一个数据对space_sem进行P操作,对data_sem进行V操作;反之,消费者进行相反的操作。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //pc.hpp 2 #ifndef __PC_HPP_ 3 #define __PC_HPP_ 4 5 #include <iostream> 6 #include <vector> 7 #include <pthread.h> 8 #include <semaphore.h> 9 using namespace std; 10 11 #define NUM 6 12 13 class CircleQueue 14 { 15 private: 16 vector<int> rq; //用数组模拟环形队列,对下标取模来模拟环状 17 size_t cap; 18 sem_t data_sem; //队列中空位数 19 sem_t space_sem; //队列中数据个数 20 size_t product_step; 21 size_t consume_step; 22 23 public: 24 CircleQueue(size_t _cap = NUM) 25 :rq(_cap) 26 , cap(_cap) 27 { 28 sem_init(&data_sem, 0, 0); 29 sem_init(&space_sem, 0, cap); 30 product_step = consume_step = 0; 31 } 32 33 ~CircleQueue() 34 { 35 sem_destroy(&data_sem); 36 sem_destroy(&space_sem); 37 } 38 39 void PushData(const int& val) 40 { 41 sem_wait(&space_sem); //space--,P 42 rq[product_step] = val; 43 ++product_step; 44 product_step %= cap; 45 sem_post(&data_sem); //data++,V 46 } 47 48 void PopData(int& val) 49 { 50 sem_wait(&data_sem); //data-- 51 val = rq[consume_step]; 52 ++consume_step; 53 consume_step %= cap; 54 sem_post(&space_sem); //space++ 55 } 56 }; 57 58 #endif //__PC_HPP_
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //pc.cc 2 #include "pc.hpp" 3 #include <ctime> 4 #include <unistd.h> 5 #include <cstdlib> 6 7 void *Producter(void *arg) 8 { 9 CircleQueue *cq = (CircleQueue *)arg; 10 while(1) 11 { 12 int data = rand() % 100 + 1; 13 cq->PushData(data); 14 cout << "Product data: " << data << endl; 15 sleep(1); //1秒生产一个 16 } 17 return (void *)0; 18 } 19 20 void *Consumer(void *arg) 21 { 22 CircleQueue *cq = (CircleQueue *)arg; 23 int data; 24 while(1) 25 { 26 cq->PopData(data); 27 cout << "Consume data: " << data << endl; 28 // sleep(1); //1秒消费一个 29 } 30 return (void *)0; 31 } 32 33 int main() 34 { 35 srand((unsigned int)time(NULL)); 36 pthread_t c, p; 37 CircleQueue cq; 38 pthread_create(&p, NULL, Producter, (void *)&cq); 39 pthread_create(&c, NULL, Consumer, (void *)&cq); 40 41 pthread_join(p, NULL); 42 pthread_join(c, NULL); 43 return 0; 44 }
当一个生产者线程对space--(P操作)后,就拥有了一个空位的使用权,该位置不会被其他线程占用;当一个消费者线程对data--时,就拥有了一个数据的使用权,这个数据不会再被其他线程使用。