生产者与消费者模型
生产者与消费者模型------针对典型的应用场景设计的解决方案
生产者与消费者模型应用场景:有线程不断的生产数据,有线程不断地处理数据
数据的生产与数据的处理,放在同一个线程中完成,因此执行流只有一个,那么肯定是生产一个处理一个,处理完一个后才能生产。这样的话依赖关系太强----如果处理比较慢,也会拖的生产速度慢下来
因此将生产 与 处理放到不同的执行流中完成,中间增加一个数据缓冲区,做为中间的数据缓冲场所。
生产者与消费者模型解决的问题:
- 解耦合
- 支持忙闲不均
- 支持并发
生产者与消费者模型的第一种实现:
生产者与消费者,其实只是两种业务处理的线程而已–创建线程就ok
实现的关键在于线程安全的队列
封装一个线程安全的BlockQueue-阻塞队列–向外提供线程安全的入队/出队操作
class BlockQueue
{
public:
BlockQueue();
~BlockQueue();
//编码风格:纯输入参数-const &/输出型参数 指针/输入输出型 &
bool Push(int data);//入队数据
bool Pop(int *data);//出队数据
private:
std::queue<int> _queue;//非线程安全的--STL奔着性能
int _capacity;//队列中节点的最大数量
pthread_mutex_t _mutex;
pthread_cond_t _productor_cond;
pthread_cond_t _customer_cond;
};
具体代码:
#include <cstdio>
#include <iostream>
#include <queue>
#include <pthread.h>
//线程安全的阻塞队列--没有数据则消费者阻塞/数据满了则生产者阻塞
#define QUEUE_MAX 5
class BlockQueue
{
public:
BlockQueue(int maxq = QUEUE_MAX):_capacity(maxq){
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_pro_cond, NULL);
pthread_cond_init(&_cus_cond, NULL);
}
~BlockQueue(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_pro_cond);
pthread_cond_destroy(&_cus_cond);
}
bool Push(int data) {
//生产者才会入队数据,如果队列中数据满了则需要阻塞
pthread_mutex_lock(&_mutex);
//_queue.size() 获取队列节点个数
while (_queue.size() == _capacity) {
pthread_cond_wait(&_pro_cond, &_mutex);
}
_queue.push(data);//_queue.push() 入队操作
pthread_mutex_unlock(&_mutex);//解锁
pthread_cond_signal(&_cus_cond);//唤醒消费者
return true;
}
bool Pop(int *data) {
//出队都是消费者,有数据才能出队,没有数据要阻塞
pthread_mutex_lock(&_mutex);
//_queue.empty() 若queue为NULL则 返回true
while (_queue.empty()) {
pthread_cond_wait(&_cus_cond, &_mutex);
}
*data = _queue.front();//_queue.front() 获取队首节点数据
_queue.pop();//出队
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_pro_cond);
return true;
}
private:
std::queue<int> _queue;
int _capacity;
pthread_mutex_t _mutex;
pthread_cond_t _pro_cond;
pthread_cond_t _cus_cond;
};
void *thr_productor(void *arg)
{
//这个参数是我们的主线程传递过来的数据
BlockQueue *queue = (BlockQueue*)arg;//类型强转
int i = 0;
while(1) {
//生产者不断生产数据
queue->Push(i);//通过Push接口操作queue中的成员变量
printf("productor push data:%d\n", i++);
}
return NULL;
}
void *thr_customer(void *arg)
{
BlockQueue *queue = (BlockQueue*)arg;
while(1) {
//消费者不断获取数据进行处理
int data;
queue->Pop(&data);
printf("customer pop data:%d\n", data);
}
return NULL;
}
int main()
{
int ret, i;
pthread_t ptid[4], ctid[4];
BlockQueue queue;
for (i = 0; i < 4; i++) {
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
if (ret != 0) {
printf("create productor thread error\n");
return -1;
}
ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
if (ret != 0) {
printf("create productor thread error\n");
return -1;
}
}
for (i = 0; i < 4; i++) {
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}
生产者与消费者模型的第二种实现:
信号量:用于实现进程/线程间的同步与互斥
本质:一个计数器+pcb等待队列
同步:只要保证多个执行流之间对资源获取的合理性-----信号量通过计数器对资源进行计数,通过计数器判断线程是否符合访问条件
互斥:只要保证多个执行流之间对资源访问的安全性-----信号量通过不大于1的计数器,标识资源只有一个,实现同一时间只有一个线程能够访问。
使用流程:
- 定义信号量 sem_t
- 初始化信号量sem_init(sem_t ,int pshared,int val)
- 在访问资源之前先访问信号量判断是否符合访问条件,若不满足则阻塞:
sem_wait(sem_t );
sem_trywait(sem_t );
sem_timedwait(); - 在促使访问资源条件满足之后,计数+1,唤醒阻塞的线程:sem_post(sem_t );
- 销毁信号量 sem_destroy(sem_t );
#include <cstdio>
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#define QUEUE_MAX 5
class RingQueue{
public:
RingQueue(int maxq = QUEUE_MAX):_queue(maxq), _capacity(maxq),
_step_read(0), _step_write(0){
//sem_init(信号量, 进程/线程标志, 信号量初值)
sem_init(&_lock, 0, 1);//用于实现互斥锁
sem_init(&_sem_data, 0, 0);//数据空间计数初始为0
sem_init(&_sem_idle, 0, maxq);//空闲空间计数初始为节点个数
}
~RingQueue(){
sem_destroy(&_lock);
sem_destroy(&_sem_data);
sem_destroy(&_sem_idle);
}
bool Push(int data){
//1. 判断是否能够访问资源,不能访问则阻塞
sem_wait(&_sem_idle);//-空闲空间计数的判断,空闲空间计数-1
//2. 能访问,则加锁,保护访问过程
sem_wait(&_lock);//lock计数不大于1,当前若可以访问则-1,别人就不能访问了
//3. 资源的访问
_queue[_step_write] = data;
_step_write = (_step_write + 1) % _capacity;//走到最后,从头开始
//4. 解锁
sem_post(&_lock);//lock计数+1,唤醒其它因为加锁阻塞的线程
//5. 入队数据之后,数据空间计数+1,唤醒消费者
sem_post(&_sem_data);
return true;
}
bool Pop(int *data){
sem_wait(&_sem_data);//有没有数据
sem_wait(&_lock); // 有数据则加锁保护访问数据的过程
*data = _queue[_step_read]; //获取数据
_step_read = (_step_read + 1) % _capacity;
sem_post(&_lock); // 解锁操作
sem_post(&_sem_idle);//取出数据,则空闲空间计数+1,唤醒生产者
return true;
}
private:
std::vector<int> _queue; // 数组, vector需要初始化节点数量
int _capacity; // 这是队列的容量
int _step_read; // 获取数据的位置下标
int _step_write;//写入数据的位置下标
//这个信号量用于实现互斥
sem_t _lock ;
//这个信号量用于对空闲空间进行计数
//---对于生产者来空闲空间计数>0的时候才能写数据 --- 初始为节点个数
sem_t _sem_idle;
// 这个信号量用于对具有数据的空间进行计数
// ---对于消费者来说有数据的空间计数>0的时候才能取出数据 -- 初始为0
sem_t _sem_data;
};
void *thr_productor(void *arg)
{
//这个参数是我们的主线程传递过来的数据
RingQueue *queue = (RingQueue*)arg;//类型强转
int i = 0;
while(1) {
//生产者不断生产数据
queue->Push(i);//通过Push接口操作queue中的成员变量
printf("productor push data:%d\n", i++);
}
return NULL;
}
void *thr_customer(void *arg)
{
RingQueue *queue = (RingQueue*)arg;
while(1) {
//消费者不断获取数据进行处理
int data;
queue->Pop(&data);
printf("tid:%p customer pop data:%d\n", pthread_self(), data);
}
return NULL;
}
int main()
{
int ret, i;
pthread_t ptid[4], ctid[4];
RingQueue queue;
for (i = 0; i < 4; i++) {
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
if (ret != 0) {
printf("create productor thread error\n");
return -1;
}
ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
if (ret != 0) {
printf("create productor thread error\n");
return -1;
}
}
for (i = 0; i < 4; i++) {
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}