特点
"321原则"
3:3种关系:生产者之间,消费者之间,生产者和消费者之间
- 生产者之间:互斥关系
- 消费者之间:互斥关系
- 生产者和消费者之间:互斥&&同步关系
2:2种角色:生产者和消费者
1:一个交易场合
生产者和消费者模型就好比去超市买东西:

有了交易场所,生产者就可以把生产多的货,存储到交易场所中,即使生产者不生产了,只要交易场所中有货,那么消费者可以直接从交易场所中取货。
为什么使用生产者-消费者模型
生产者-消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
优点
- 解耦:生产者和消费者完全独立,互不依赖
- 支持并发
- 支持忙闲不均

基于BlockingQueue的生产者消费者模型
BlockingQueue
在多线程编程中,阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。
C++ queue模拟阻塞队列的生产消费模型

代码:
我们可以对锁和条件变量进行封装,可以让我们语言变得简洁:
Mutex.hpp
#pragma once
#include<pthread.h>
#include<mutex>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock,nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
pthread_mutex_t* Get()
{
return &_lock;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex*lock):_lock(lock)
{
_lock->Lock();
}
~LockGuard()
{
_lock->Unlock();
}
private:
Mutex*_lock;
};
Cond.hpp
#pragma once
#include <pthread.h>
#include <iostream>
#include "Mutex.hpp"
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void NotifyAll()
{
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
void NotifyOne()
{
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Wait(Mutex &lock)
{
pthread_cond_wait(&_cond, lock.Get());
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
BlockQueue.hpp
#pragma once
#include<iostream>
#include<pthread.h>
#include<queue>
#include<string>
#include<unistd.h>
#include<mutex>
#include"Cond.hpp"
#include"Mutex.hpp"
const static uint32_t gcap=5;
template<typename T>
class BlockQueue
{
private:
bool IsFull()
{
return _bq.size()>=_cap;
}
bool IsEmpty()
{
return _bq.empty();
}
public:
BlockQueue(uint32_t cap=gcap)
:_cap(cap)
,_c_wait_num(0)
,_p_wait_num(0)
{
}
~BlockQueue()
{
}
void Pop(T*out)
{
LockGuard lock(&_lock);
while(IsEmpty())
{
_c_wait_num++;
_c_cond.Wait(_lock);
_c_wait_num--;
}
*out=_bq.front();
_bq.pop();
if(_p_wait_num>0)
{
_p_cond.NotifyAll();
}
}
//进队列
void Push(const T&in)
{
//上锁
LockGuard lock(&_lock);
while(IsFull())//保证健壮性
{
++_p_wait_num;
_p_cond.Wait(_lock);
_p_wait_num--;
}
//不满的
_bq.push(in);//完成生产
if(_c_wait_num>0)
{
_c_cond.NotifyOne();
}
}
private:
uint32_t _cap;//容量
std::queue<T> _bq;//阻塞队列,即存储生产的东西
Mutex _lock;//锁
Cond _c_cond;//消费者用的条件变量
Cond _p_cond;//生产者用的条件变量
int _c_wait_num;//消费者数量
int _p_wait_num;//生产者数量
};
当然,阻塞队列中不单单可以放整型,浮点型,字符串等等,还可以放任务,这样就是线程池的原理。
Task.hpp
#pragma once
#include<unistd.h>
#include<iostream>
#include<functional>
using func_t=std::function<void()>;
void PrintLog()
{
std::cout<<"我是一个日志任务"<<std::endl;
}
#include"BlockQueue.hpp"
#include"Task.hpp"
#include<iostream>
struct Data
{
std::string name;
BlockQueue<func_t>*q;
};
void* comsumer(void* arg)
{
Data*td=static_cast<Data*>(arg);
while(true)
{
sleep(1);
func_t t;
td->q->Pop(&t);
t();
}
return (void*)0;
}
void* producer(void* arg)
{
Data*td=static_cast<Data*>(arg);
//int i=1, j=0;
while(true)
{
sleep(1);
td->q->Push(PrintLog);
std::cout<<"生产了一个任务"<<std::endl;
}
return (void*)0;
}
//生产者消费者模型
int main()
{
BlockQueue<func_t> *bq=new BlockQueue<func_t>();
//单消费者,单生产者
// pthread_t c;
// pthread_t p;
// Data ctd={"comsumer",bq};
// Data ptd={"producer",bq};
// pthread_create(&c,nullptr,comsumer,(void*)&ctd);
// pthread_create(&p,nullptr,producer,(void*)&ptd);
// pthread_join(c,NULL);
// pthread_join(p,NULL);
//多消费者,多生产者
pthread_t c[3];
pthread_t p[2];
Data ctd={"comsumer",bq};
Data ptd={"producer",bq};
pthread_create(c,nullptr,comsumer,(void*)&ctd);
pthread_create(c+1,nullptr,comsumer,(void*)&ctd);
pthread_create(c+2,nullptr,comsumer,(void*)&ctd);
pthread_create(p,nullptr,producer,(void*)&ptd);
pthread_create(p+1,nullptr,producer,(void*)&ptd);
pthread_join(*c,NULL);
pthread_join(*p,NULL);
delete bd;
return 0;
}
POSIX信号量
概念
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源⽬的。但POSIX可以用于线程间同步。
同步本质:在任意时刻,都只有一个执行流在临界区中。
信号量主要统计可用资源的数量。
比如一家电影院,里面的位置有100个,电影院是买了票的才有资格进入,电影院是个临界区,那么里面的100个就是可以进入该临界区享受临界资源的人,而这一百个座位就是信息量。电影院里的位置是可以预定的,当有人预定的时候,电影院的位置就少一个,即信息量也少一个,当有人离开电影院的时候,电影院的位置就多一个。当然,买了票的人可以一次多人进入,那么线程进入临界区也可以多个线程同时进入,但是最多可以进入信息量中统计的可用资源的数量。
总结:
- 总共有100个座位 = 信号量初始值是100
- 每卖出一张票 = 信号量减1(P操作)
- 每有观众离场 = 信号量加1(V操作)
- 座位卖光了 = 信号量为0,新观众要等待
有关信息量的函数:
初始化信息量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
销魂信息量
int sem_destroy(sem_t *sem);
等待信号量(P操作)
功能:申请信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
发布信号量(V操作)
功能:释放信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
封装信号量
#pragma once
#include<semaphore.h>
class Sem
{
public:
Sem(int num):_initnum(num)
{
sem_init(&_sem,0,_initnum);
}
~Sem()
{
sem_destroy(&_sem);
}
void P()//申请信息量
{
int n=sem_wait(&_sem);
(void)n;
}
void V()//释放信号量
{
int n=sem_post(&_sem);
(void)n;
}
private:
sem_t _sem;
int _initnum;//初始化数量
};
上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):
基于环形队列的生产消费模型
环形队列采用数组模拟,用模运算来模拟环状特性

当数据存放到尾的下一个的时候,通过index%N来实现环形。
- 为空为满的时候,首尾的下标一样。
- 不为空不为满的时候,首尾的下标不一样。
但如何分清为空和为满状态呢?
给该数组多一个空间,当(tail + 1) % size == head时候,该环形队列为满,当首尾下标一样的时候,该环形队列为空。
现在我们用这个信息量这个计数器就可以实现。
信息量可以知道是否还有资源、空间可以用。
代码:
#ifndef _RINGQUEUE_HPP_
#define _RINGQUEUE_HPP_
#include "Sem.hpp"
#include <iostream>
#include <pthread.h>
#include <vector>
#include "Mutex.hpp"
const int gcap = 5;
template <typename T>
class RingQueue
{
public:
RingQueue(int cap = gcap)
: _ringQueue(cap), _cap(cap), _space_sem(cap), _data_sem(0)
, _c_step(0), _p_step(0)
{
}
void Push(const T&in)
{
_space_sem.P();
{
LockGuard lockguard(&_p_lock);
_ringQueue[_p_step++]=in;
_p_step%=_cap;
}
_data_sem.V();
}
void Pop(T*out)
{
_data_sem.P();
{
LockGuard lockguard(&_c_lock);
*out=_ringQueue[_c_step++];
_c_step%=_cap;
}
_space_sem.V();
}
~RingQueue()
{
}
private:
std::vector<T> _ringQueue;
int _cap;
Sem _space_sem; // 管理空间的信息量
Sem _data_sem; // 管理数据的信息量
Mutex _c_lock; // 消费者的锁
Mutex _p_lock; // 生产者的锁
int _c_step; // 消费者的位置
int _p_step; // 生产者的位置
};
#endif