建设综合购物网站,怎样学电商赚钱,主流媒体网站建设,网页设计的实训报告【Linux多线程】生产者消费者模型 目录 【Linux多线程】生产者消费者模型生产者消费者模型为何要使用生产者消费者模型生产者消费者的三种关系生产者消费者模型优点基于BlockingQueue的生产者消费者模型C queue模拟阻塞队列的生产消费模型 伪唤醒情况#xff08;多生产多消费的…【Linux多线程】生产者消费者模型 目录 【Linux多线程】生产者消费者模型生产者消费者模型为何要使用生产者消费者模型生产者消费者的三种关系生产者消费者模型优点基于BlockingQueue的生产者消费者模型C queue模拟阻塞队列的生产消费模型 伪唤醒情况多生产多消费的情况下 作者爱写代码的刚子 时间2024.3.29 前言本篇博客将会介绍Linux多线程中一个非常重要的模型——生产者消费者模型 生产者消费者模型 321原则方便记忆3种关系2种角色生产者和消费者1个交易场所特定结构的内存空间 为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者的三种关系 生产者VS生产者 互斥消费者VS消费者互斥生产者VS消费者互斥同步 生产者消费者模型优点 生产和消费进行解耦多线程其实也是一种解耦支持并发支持忙闲不均 基于BlockingQueue的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构有点类似于管道。其与普通的队列区别在于当队列为空时从队列获取元素的操作将会被阻塞直到队列中被放入了元素;当队列满时往队列里存放元素的操作也会被阻塞直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的线程在对阻塞队列进程操作时会被阻塞) C queue模拟阻塞队列的生产消费模型
代码
以下代码以单生产者单消费者为例:
代码一
#pragma once
#include iostream
#include queue
#include pthread.h
template class T
class BlockQueue
{static const int defalutnum 5;
public:BlockQueue(int maxcapdefalutnum):maxcap_(maxcap){pthread_mutex_init(mutex_,nullptr);pthread_cond_init(c_cond_,nullptr);pthread_cond_init(p_cond_,nullptr);}T pop(){pthread_mutex_lock(mutex_);if(q_.size() 0 ){pthread_cond_wait(c_cond_,mutex_);//生产和消费要使用不同的等待队列}T out q_.front();q_.pop();pthread_cond_signal(p_cond_);pthread_mutex_unlock(mutex_);return out;}void push(const T in){pthread_mutex_lock(mutex_);if(q_.size()maxcap_)//判断本身也是访问临界资源{pthread_cond_wait(p_cond_,mutex_);//调度时自动释放锁}//1.队列没满 2.被唤醒q_.push(in); pthread_cond_signal(c_cond_);pthread_mutex_unlock(mutex_);}~BlockQueue(){pthread_mutex_destroy(mutex_);pthread_cond_destroy(c_cond_);pthread_cond_destroy(p_cond_);}
private:std::queueT q_;//我们不直接使用stl中的queue是因为它本身不是线程安全的共享资源//int mincap_;int maxcap_;//队列中的极值pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;
};#include BlockQueue.hpp
#include unistd.h
void *Consumer(void *args)
{BlockQueueint *bq static_castBlockQueueint*(args); while(true){sleep(2);// 由于两个线程谁先执行是不确定的我们让生产者先执行int data bq-pop();std::cout消费了一个数据: datastd::endl;}
}
void *Productor(void *args)
{BlockQueueint *bq static_castBlockQueueint*(args); int data0;while(true){data;bq-push(data);std::cout生产了一个数据: datastd::endl;}
}
int main()
{BlockQueueint *bq new BlockQueueint(); pthread_t c,p;pthread_create(c,nullptr,Consumer,bq);pthread_create(p,nullptr,Productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}调整代码使其生产者生产的数据到达一定范围通知消费者消费者消费了一定的数据通知生产者 【问】生产者的数据从哪里来 用户或者网络等。生产者生产的数据也是要花时间获取的所以生产者要做两件事1. 获取数据 2. 生产数据到队列 同时消费者拿到数据要做加工处理也要花时间消费者要做两件事1. 消费数据 2. 加工处理数据 【问】生产者消费者模型为什么是高效的
存在一个线程访问临界区的代码一个线程正在处理数据高效并发。
虽然互斥和同步谈不上高效更何况加了锁但是一个线程正在生产数据一个线程正在消费数据两者解偶且互不影响。在更多的生产者消费者情况下只有少量的执行流在互斥和同步而大量的执行流都在并发访问
再次完善代码使该生产者消费者模型能够执行相应的任务
BlockQueue.hpp
#pragma once
#include iostream
#include queue
#include pthread.h
template class T
class BlockQueue
{static const int defalutnum 20;
public:BlockQueue(int maxcapdefalutnum):maxcap_(maxcap){pthread_mutex_init(mutex_,nullptr);pthread_cond_init(c_cond_,nullptr);pthread_cond_init(p_cond_,nullptr);low_water_ maxcap_/3;high_water_ (maxcap_*2)/3;}T pop(){pthread_mutex_lock(mutex_);if(q_.size() 0 ){pthread_cond_wait(c_cond_,mutex_);//生产和消费要使用不同的等待队列}T out q_.front();q_.pop();if(q_.size()low_water_){pthread_cond_signal(p_cond_);}pthread_mutex_unlock(mutex_);return out;}void push(const T in){pthread_mutex_lock(mutex_);if(q_.size()maxcap_)//判断本身也是访问临界资源{pthread_cond_wait(p_cond_,mutex_);//调度时自动释放锁}//1.队列没满 2.被唤醒q_.push(in); if(q_.size()high_water_){pthread_cond_signal(c_cond_);}pthread_mutex_unlock(mutex_);}~BlockQueue(){pthread_mutex_destroy(mutex_);pthread_cond_destroy(c_cond_);pthread_cond_destroy(p_cond_);}private:std::queueT q_;//我们不直接使用stl中的queue是因为它本身不是线程安全的共享资源//int mincap_;int maxcap_;//队列中的极值pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;int high_water_;int low_water_;
};main.cc
#include BlockQueue.hpp
#include Task.hpp
#include unistd.h
void *Consumer(void *args)
{BlockQueueTask *bq static_castBlockQueueTask*(args); while(true){Task tbq-pop();//t.run();t();std::cout处理任务 t.GetTask() 运算结果是: t.GetResult()std::endl;//sleep(2);// 由于两个线程谁先执行是不确定的我们让生产者先执行//std::cout消费了一个数据: datastd::endl;}
}
void *Productor(void *args)
{int len opers.size();BlockQueueTask *bq static_castBlockQueueTask*(args); int data0;while(true){int data1rand()%101;usleep(10);int data2rand() % 10;char op opers[rand() % len];Task t(data1,data2,op);//data;bq-push(t);//std::cout生产了一个数据: datastd::endl;std::cout生产了一个任务 t.GetTask() std::endl;sleep(1);}
}
int main()
{srand(time(nullptr));BlockQueueTask *bq new BlockQueueTask(); pthread_t c,p;pthread_create(c,nullptr,Consumer,bq);pthread_create(p,nullptr,Productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}Task.hpp
#pragma once
#include iostream
#include stringstd::string opers -*/%;enum
{DivZero 1,ModZero,Unknown
};class Task
{
public:Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case :result_ data1_ data2_;break;case -:result_ data1_ - data2_;break;case *:result_ data1_ * data2_;break;case /:{if (data2_ 0)exitcode_ DivZero;elseresult_ data1_ / data2_;}break;case %:{if (data2_ 0)exitcode_ ModZero;elseresult_ data1_ % data2_;}break;default:exitcode_ Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r std::to_string(data1_);r oper_;r std::to_string(data2_);r ;r std::to_string(result_);r [code: ;r std::to_string(exitcode_);r ];return r;}std::string GetTask(){std::string r std::to_string(data1_);roper_;r std::to_string(data2_);r ?;return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};一定要记得判断临界资源是否满足也是在访问临界资源
伪唤醒情况多生产多消费的情况下
多生产多消费的情况下 举例生产者只生产了一个数据但是唤醒了多个消费者多个消费者都在等待队列上生产者将锁解开多个消费者竞争这一把锁其中一个消费者抢到了这把锁消费了一个数据把锁解开同时其他刚被唤醒的消费者其中又抢到了锁进行消费可是已经没有数据了条件并不满足了造成了伪唤醒的情况。处于等待队列中的线程申请锁失败了会继续在条件变量中的等待队列中等 或者说可能存在等待失败但是继续向下走的情况。 如何防止线程出现这种情况
将if改成while(进行重复判断): 【问题】无论是多生产多消费还是单生产单消费本质上都是一个线程访问临界资源那意义在哪
重点是并发生产并发消费只是访问临界资源时是单个线程。重点不是获取数据本身而在于处理数据本质