生产者消费者模型&&POSIX信号量&&线程池
生产者消费者模型
1.概念的引入
在日常生活中,当我们缺少生活用品时,我们会去超市购物,当我们买走超市货架上的东西,超市则会补充货架上的货物,那么超市的货物是从哪来的呢?不错,他们会有自己的供货商。那么我们是以怎样的身份进入超市呢?相信每个人都知道自己是以消费者的角色去的,生产者毫无疑问就是供货商,那么将我们与供货商这二者联系到一起的超市在这里面又扮演了什么样的角色呢?仔细想想便不难得出超市就是交易场所。
所以我们总结”321原则“来描述生产者消费者模型:
- 三种关系:生产者 vs 生产者(互斥关系) ,生产者vs消费者(同步关系) ,消费者vs消费者(互斥关系)
- 两种角色:生产者&&消费者
- 一个交易场所:容器(一般用队列,交互的是数据)
2.为什么要使用生产者消费者模型
- 能够实现逻辑在代码层面上的解耦
- 可以适配生产消费的速度
- 保证代码的维护性
- 支持并发,支持忙闲不均
POSIX信号量
1.作用
- 用于同步操作,达到无冲突的访问共享资源的目的。
- 信号量本质是个计数器
2.相关函数
- 初始化信号量
#include< semaphore.h>
int _sem_init(sem_t *sem,int pshared,unsigned int value)
参数:
pthread: 0表示线程间共享,非零表示进程间共享
value : 信号量初始值
- 销毁信号量
int sem_destroy(sem_t *sem)
- 等待信号量 (P操作)
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
- 发布信号量(V操作)
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量的值加1.
int sem_post(sem_t *sem);
3. 运用(基于环形队列的生产者消费者模型)
//RingQueue.hpp
#pragma once
#include<iostream>
#include<vector>
#include<unistd.h>
#include<semaphore.h>
#define NUM 10
class RingQueue{
private:
std::vector<int> v;
int max_cap;
sem_t sem_blank; //生产者(空格子数)
sem_t sem_data; //消费者(数据数目)
int c_index;
int p_index;
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
public:
RingQueue(int _cap=NUM)
:max_cap(_cap)
,v(_cap),c_index(0),p_index(0)
{
sem_init(&sem_blank,0,max_cap);
sem_init(&sem_data,0,0);
}
void Get(int &out)
{
P(sem_data);
//消费
out=v[c_index];
c_index++;
c_index%=max_cap;
V(sem_blank);
}
void Put(const int &in)
{
P(sem_blank);
//生产
v[p_index++]=in;
p_index%=max_cap;
V(sem_data);
}
~RingQueue()
{
sem_destroy(&sem_blank);
sem_destroy(&sem_data);
c_index=0;
p_index=0;
}
};
// main.cc
#include"RingQueue.hpp"
void *consumer(void *ring_queue)
{
RingQueue *rq=(RingQueue*)ring_queue;
while(true){
int data=0;
rq->Get(data);
std::cout<<"consumer done...#"<<data<<std::endl;
}
}
void*productor(void*ring_queue)
{
RingQueue *rq=(RingQueue*)ring_queue;
int count=100;
while(true){
sleep(1);
rq->Put(count);
count++;
if(count>110)
count=100;
std::cout<<"productor done...#"<<std::endl;
}
}
int main()
{
pthread_t c,p;
RingQueue *rq=new RingQueue();
pthread_create(&c,nullptr,consumer,rq);//单消费者
pthread_create(&p,nullptr,productor,rq);//单生产者
pthread_join(c,nullptr);
pthread_join(p,nullptr);
delete rq;
return 0;
}
结果展示:
线程池
介绍:
一种线程的使用模式。
线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着 监督管理者分配可并发执行的任务。
这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利 用,还能防止过分调度。可用线程数量应该取决于
可用的并发处理器、处理器内核、内存、网络sockets等的数量。
应用场景:
-
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技 术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
-
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
-
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情 况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误
* 线程池示例:
-
- 创建固定数量线程池,循环从任务队列中获取任务对象,
-
- 获取到任务对象后,执行任务对象中的任务接口
//ThreadPool.hpp
#pragma once
#include<iostream>
#include<queue>
#include<math.h>
#include<unistd.h>
#define NUM 5
class Task{
public:
int base;
public:
Task(){}
Task(int _b):base(_b){}
void Run()
{
std::cout<<"task run...done: base#"<<base<<" "<<"pow is#"<<pow(base,2)<<std::endl;
}
~Task(){}
};
class ThreadPool{
private:
std::queue<Task*> q;
int max_num;
pthread_mutex_t lock;
pthread_cond_t cond; //only consumer,thread pool thread;
public:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
bool IsEmpty()
{
return q.size()==0;
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeUp()
{
pthread_cond_signal(&cond);//唤醒一个线程
//pthread_cond_broadcast(&cond);唤醒一群
}
public:
ThreadPool(int _max=NUM):max_num(_max)
{}
static void *Routine(void *arg)//static 只能访问static
{
ThreadPool *this_p=(ThreadPool*)arg;
pthread_detach(pthread_self());
while(true){
this_p->LockQueue();//保护任务队列
while(this_p->IsEmpty()){//判断任务队列是否为空
this_p->ThreadWait();
}
Task t;
this_p->Get(t);
this_p->UnlockQueue();
t.Run();
}
}
void ThreadPoolInit()//避免风险
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
pthread_t t;
for(int i=0;i<max_num;i++){
pthread_create(&t,nullptr,Routine,this);
}
}
//server
void Put(Task &in)
{
LockQueue();
q.push(&in);
UnlockQueue();
ThreadWakeUp();
}
//Thread poll
void Get(Task &out)//本身在临界区,已经被锁住了
{
Task*t=q.front();
q.pop();
out=*t;
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
//main.cc
#include"ThreadPool.hpp"
int main()
{
ThreadPool *tp=new ThreadPool();
tp->ThreadPoolInit();
//server
while(true){
int x=rand()%10+1;
Task t(x);
tp->Put(t);
sleep(1);
}
return 0;
}
结果展示:
线程池存在的价值:
- 有任务立马有线程进行服务,省掉了线程创建时间。
- 有效防止服务器中线程过多,导致系统过载问题。