1. 概念
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯, 而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个 阻塞队列就是用来给生产者和消费者解耦的。
简单概括:一个场所,两种角色,三种关系
关系:生产者与生产者:互斥
消费者与消费者:互斥
生产者与消费者:同步+互斥
2. 生产者与消费者模型解决的问题:
1.解耦合
2.支持并发
3.支持忙闲不均
3. POSIX标准信号量:
4.1 作用:实现进程/线程间的同步与互斥
4.2 本质:一个计数器+pcb等待队列
4.3 实现同步:
计数器对资源数量进行计数,当线程想要获取资源的时候,先访问信号量,判断是否能够获取(信号量通过自身的计数完成判断)
若计数<=0直接阻塞线程,计数-1;其他线程生产资源之后,计数+1,唤醒等待队列上的pcb
4.4 实现互斥:
保证计数器的数值不会大于1,就表示同一时间只有一个线程能够访问资源
4.5 操作接口:
1.定义信号量
sem_t sem;
2.初始化信号量
int sem_init(sem_t *sem,int pshared,int value);
参数:
pshared:信号量既可用于进程间也可用于线程间,pshared为0表示用于线程间(用全局变量来实现的),非0表示用于进程间
value:信号量就是一个计数器,统计资源数量,value就是通过资源数量初始化计数器的
3.在访问临界资源之前,先判断计数,是否能够访问资源,若不能访问,则阻塞线程,若可以访问则调用直接返回
int sem_wait(sem_t* sem)/int sem_timedwait(sem_t* sem)/sem_trywait(sem_t* sem,struct timespec *ts);
4.访问临界资源之后/生产资源之后,唤醒一个等待的进程,并且计数+1
int sem_post(sem_t *sem);
5.不使用信号量记得释放资源
int sem_destroy(sem_t *sem);
4.6 信号量的应用:
通过信号量实现一个环形队列,最终实现一个生产者与消费者模型
4. 生产者与消费者模型的实现:
生产者与消费之只是不同角色的执行流:
只需要实现线程安全的队列,然后各自创建不同角色的执行流就可以实现这个模型
线程安全的阻塞队列的实现:
使用C++封装实现了一个阻塞队列类,STL中的std::queue这个队列是非线程安全的
#include <cstdio>
#include <iostream>
#include <queue>
#include <pthread.h>
#include <semaphore.h>
#define MAX_QUEUE 5
#define MAX_THREAD 5
class RingQueue{
private:
std::vector<int> _queue;
int _capacity;
int _step_read; // 当前即将读取数据的位置的下标
int _step_write; // 当前即将写入数据的位置的下标
sem_t _sem_lock;// 用于实现互斥的信号量
// 使用这个计数器,实现对当前队列中的数据资源的数量进行计数;
// 如果<=0表示没有资源,则消费者会陷入等待
sem_t _sem_data;
// 使用这个计数器,实现对当前队列 中的空闲空间数量进行计数;
// 如果<=0表示队列满了,则生产者陷入等地啊
sem_t _sem_space;
public:
RingQueue(int max = MAX_QUEUE) : _queue(max), _capacity(max),
_step_read(0), _step_write(0){
//...初始化过程...
//互斥信号量的初始化
//pshare设置为0,表示当前用于线程间的同步互斥
//value信号量初值,初始化为1,数值最大不大于1,实现互斥
sem_init(&_sem_lock, 0, 1);
sem_init(&_sem_data, 0, 0);//数据资源初始为0
sem_init(&_sem_space, 0, max);//空闲空间初值就是节点数量
}
~RingQueue() {
//...销毁资源过程...
sem_destroy(&_sem_lock);
sem_destroy(&_sem_data);
sem_destroy(&_sem_space);
}
bool Push(int data) {
sem_wait(&_sem_space);
//统计空间节点数量,自动判断是否有空闲空间,没有则阻塞
//互斥保护临界资源,注意上边的信号量不需要保护
//若保护了上边的信号量操作,反而回出问题:若先加锁再判断,有可能会陷入休眠而没解锁
sem_wait(&_sem_lock);
_queue[_step_write] = data;
_step_write = (_step_write + 1) % _capacity;
sem_post(&_sem_lock);
sem_post(&_sem_data);//入队数据之后,数据资源的数量增加一个,并且唤醒消费者
return true;
}
bool Pop(int *data) {
sem_wait(&_sem_data);//消费者判读数据资源数量,若<=0则回陷入等待 ,计数-1
sem_wait(&_sem_lock);
*data = _queue[_step_read];
_step_read = (_step_read + 1) % _capacity;
sem_post(&_sem_lock);
sem_post(&_sem_space);//空间资源多了一个,唤醒生产者
return true;
}
};
void *thr_consumer(void *arg)
{
RingQueue *q = (RingQueue*)arg;
while(1) {
//消费者处理数据
int data;
q->Pop(&data);
printf("消费者:%p 出队数据:%d\n", pthread_self(), data);
}
return NULL;
}
void *thr_productor(void *arg)
{
RingQueue *q = (RingQueue*)arg;
int i = 0;
while(1) {
//生产者生产数据
q->Push(i);
printf("生产者:%p 入队数据:%d\n", pthread_self(), i++);
}
return NULL;
}
int main()
{
//完成生产者与消费者模型
//创建生产者角色的线程以及消费者角色的线程
pthread_t ctid[MAX_THREAD], ptid[MAX_THREAD];
int ret , i;
RingQueue q;
//创建消费者线程
for (i = 0; i < MAX_THREAD; i++) {
ret = pthread_create(&ctid[i], NULL, thr_consumer, (void*)&q);
if (ret != 0) {
printf("thread create error\n");
return -1;
}
}
//创建生产者线程
for (i = 0; i < MAX_THREAD; i++) {
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&q);
if (ret != 0) {
printf("thread create error\n");
return -1;
}
}
//等待所有线程退出
for (i = 0; i < MAX_THREAD; i++) {
pthread_join(ctid[i], NULL);
pthread_join(ptid[i], NULL);
}
return 0;
}