1.为何会产生生产者-消费者
面试手撕生产者-消费者模型!!!
为何要使用生产者消费者模型,并发场景下,多线程生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理, 直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
2.生产者消费者模型优点
- 解耦
- 支持并发
- 支持忙闲不均
3.queue模拟阻塞队列
基于BlockingQueue的生产者消费者模型,BlockingQueue 在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会 被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取 出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
C++ queue模拟阻塞队列的生产消费模型(单生产者,单消费者) 代码:
#include<iostream>
#include<queue>
#include<stdlib.h>
#include<pthread.h>
#define MUM 8
class BlockQueue{
private:
std::queue<int> q;
int cap;
pthread_mutex_t lock;
pthread_cond_t full;
pthread_cond_t empty;
private:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnLockQueue()
{
pthread_mutex_unlock(&lock);
}
void ProductWait()
{
pthread_cond_wait(&full, &lock);
}
void ConsumeWait()
{
pthread_cond_wait(&empty, &lock);
}
void NotifyProduct()
{
pthread_cond_signal(&full);
}
void NotifConsume()
{
pthread_cond_signal(&empty);
}
bool IsEmpty()
{
return (q.size() == 0 ? true : false);
}
bool IsFull()
{
return (q.size() == cap ? true : false);
}
public:
BlockQueue(int _cap = MUM) :cap(_cap)
{
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty, NULL);
}
void PushData(const int &data)
{
LockQueue();
while (IsFull())
{
NotifConsume();
std::cout << "queue is full,notify consume data, product stop." << std::endl;
ProductWait();
}
q.push(data);
NotifConsume();
UnLockQueue();
}
void PopData(int &data)
{
LockQueue();
while (IsFull())
{
NotifyProduct();
std::cout << "queue is empty, notify data , consue stop." << std::endl;
ConsumeWait();
}
data = q.front();
q.pop();
NotifConsume();
UnLockQueue();
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
};
void *consumer(void *arg)
{
BlockQueue *bqp = (BlockQueue*)arg;
int data;
for (;;)
{
bqp->PopData(data);
std::cout << "Consue data done :" << data << std::endl;
}
}
void *producter(void *arg)
{
BlockQueue *bqp = (BlockQueue*)arg;
srand((unsigned long)time(NULL));
for (;;)
{
int data = rand() % 1024;
bqp->PushData(data);
std::cout << "Prodoct data done :" << data << std::endl;
}
}
int main()
{
BlockQueue bq;
pthread_t c, p;
pthread_create(&c, NULL, consumer, (void*)&bq);
pthread_create(&p, NULL, producter, (void*)&bq);
pthread_join(c, NULL);
pthread_join(p, NULL);
return 0;
}
4.环形队列
前边讲到的生产者 - 消费者的例子是基于queue的, 其空间可以动态分配, 现在基于固定大小的环形队列重写这个程序 (POSIX信号量) :基于环形队列的生产消费模型环形队列采用数组模拟,用模运算来模拟环状特性环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程;
#include<iostream>
#include<vector>
#include<stdlib.h>
#include<semaphore.h>
#include<pthread.h>
#define MUM 16
class RingQueue{
private:
std::vector<int> q;
int cap;
sem_t data_sem;
sem_t space_sem;
int consume_step;
int product_step;
public:
RingQueue(int _cap = MUM) :q(_cap), cap(_cap)
{
sem_init(&data_sem, 0, 0);
sem_init(&space_sem, 0, cap);
consume_step = 0;
product_step = 0;
}
void PutData(const int &data)
{
sem_wait(&space_sem);
q[consume_step] = data;
consume_step++;
consume_step %= cap;
sem_post(&data_sem);
}
void GetData(int &data)
{
sem_wait(&data_sem);
data = q[product_step];
product_step++;
product_step %= cap;
sem_post(&space_sem);
}
~RingQueue()
{
sem_destory(&data_sem);
sem_destory(&space_sem);
}
};
void *consumer(void *arg)
{
RingQueue *rap = (RingQueue*)arg;
int data;
for (;;)
{
rqp->GetData(data);
std::cout << "Consume data done:" << data << std::endl;
sleep(1);
}
}
void *productor(void *arg)
{
RingQueue *rqp = (RingQueue)arg;
srand((unsigned long)time(NULL));
for (;;)
{
int data = rand() % 1024;
rqp->GetData(data);
std::cout << "Prodoct data done:" << data << std::endl;
}
}
int main()
{
RingQueue rp;
pthread_t c, p;
pthread_create(&c, NULL, consumer, (void*)&rp);
pthread_create(&p, NULL, producter, (void*)&rp);
pthread_join(c, NULL);
pthread_join(p, NULL);
}