> 文章列表 > 【Linux】生产者消费者模型

【Linux】生产者消费者模型

【Linux】生产者消费者模型


目录

一、生产者消费者模型

1、生产者消费者模型的概念

2、生产者、消费者之间的关系

3、生产者和消费者的特点

二、基于BlockingQueue的生产者消费者模型(条件变量控制同步与互斥)

1、一个生产线程和一个消费线程完成的计算任务

1.1BlockQueue.hpp

1.2Task.hpp 

1.3Main.cc 

2、基于生产者消费者模型的生产、消费、存储的多线程代码

2.1BlockQueue.hpp

2.2Task.hpp

2.3Main.cc

3、基于生产者消费者模型的多生产者多消费者的多线程代码

4、生产者消费者模型真的高效吗?

三、基于环形队列的多生产者多消费者模型(信号量控制同步与互斥)

1、RingQueue.hpp

2、Task.hpp 

3、main.cc


一、生产者消费者模型

1、生产者消费者模型的概念

        生产者消费者模型是通过一个中间容器来解决生产者和消费者之间的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列进行通讯。所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列相当于一个缓冲区。

2、生产者、消费者之间的关系

三种生产者、消费者之间的关系:

  • 1、生产者和生产者之间的互斥关系
  • 2、消费者和消费者之间的互斥关系
  • 3、生产者和消费者之间的互斥与同步关系。(互斥:需要保证读写安全;同步:当缓冲区数据满了或空了,能够互相等待和通知)

两种角色:生产者线程和消费者线程

一个交易场所:一段特定结构的缓冲区

3、生产者和消费者的特点

  • 1、生产线程和消费线程进行解耦
  • 2、支持生产和消费的一段时间忙闲不均的问题
  • 3、支持并发,提高效率。例如生产者线程在缓冲区生产函数参数的时候,消费者线程也可以正常运行。这两个线程有原来的串行执行变为并发执行,提高效率。

二、基于BlockingQueue的生产者消费者模型(条件变量控制同步与互斥)

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

1、一个生产线程和一个消费线程完成的计算任务

代码结果如下:

1.1BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
const int gMaxCap=5;
template <class T>
class BlockQueue
{
public:BlockQueue(const int capacity=gMaxCap):_capacity(capacity){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_pcond,nullptr);pthread_cond_init(&_ccond,nullptr);}void push(const T& in)//输入型参数:const &{pthread_mutex_lock(&_mutex);//细节2:充当条件的判断必须是while,不能用if//这是因为唤醒的时候存在唤醒异常或伪唤醒的情况//需要让线程重新使用IsFull对空间就行判断,确保100%唤醒while(IsFull()){//细节1://该线程被pthread_cond_wait函数挂起后,会自动释放锁。//该线程被pthread_cond_signal函数唤醒后,会自动重新获取原来那把锁pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足,无法生产,此时我们的生产者进行等待}//走到这里一定没有满_q.push(in);//刚push了一个数据,可以试着消费者把他取出来(唤醒消费者)//细节3:pthread_cond_signal()这个函数,可以放在临界区内部,也可以放在临界区外部pthread_cond_signal(&_ccond);//可以设置水位线,满多少就唤醒消费者pthread_mutex_unlock(&_mutex);//pthread_cond_signal(&_ccond);//也可以放在解锁之后}void pop(T* out)//输出型参数:*  //输入输出型:&{pthread_mutex_lock(&_mutex);while(IsEmpty()){pthread_cond_wait(&_ccond,&_mutex);//消费者休眠}//先把数据处理好,再唤醒消费者*out=_q.front();_q.pop();//走到这里,一定能保证不为空,唤醒生产者进行生产pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}
private:bool IsEmpty(){return _q.empty();}bool IsFull(){return _q.size()==_capacity;}
private:std::queue<T> _q;int _capacity;//阻塞队列的容量pthread_mutex_t _mutex;//互斥锁pthread_cond_t _pcond;//生产者对应的条件变量pthread_cond_t _ccond;//消费者对应的条件变量
};

1.2Task.hpp 

#pragma once
#include <iostream>
#include <functional>
class Task
{//using func=std::function<int(int,int,char)>;typedef std::function<int(int,int,char)> func_t;//函数对象
public:Task(){}Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callBack(func){}std::string operator()(){int result=_callBack(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=%d",_x,_op,_y,result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;func_t _callBack;//回调函数
};

1.3Main.cc 

#include <iostream>
#include <queue>
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
const std::string oper="+-*/%";
int myMath(int x,int y,char op)
{int result=0;switch(op){case '+':result=x+y;break;case '-':result=x-y;break;case '*':result=x*y;break;case '/':{if(y==0){std::cerr<<"div zero"<<std::endl;result=-1;}else result=x/y;}break;case '%':{if(y==0){std::cerr<<"mod zero"<<std::endl;result=-1;}else result=x%y;}break;default:break;}return result;
}
//消费者
void* consumer(void* args)
{BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(args);while(1){//消费数据Task t;bq->pop(&t);printf("Consumed task:%s\\n",t().c_str());//sleep(1);}return nullptr;
}
//生产者
void* productor(void* args)
{BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(args);while(1){//生产数据int x=rand()%10+1;int y=rand()%5;int operCode=rand()%oper.size();Task t(x,y,oper[operCode],myMath);bq->push(t);//把任务push进阻塞队列printf("Production task:%s\\n",t.toTaskString().c_str());sleep(1);}return nullptr;
}
int main()
{srand((unsigned int)time(nullptr)^getpid());BlockQueue<Task>* bq=new BlockQueue<Task>();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;
}

2、基于生产者消费者模型的生产、消费、存储的多线程代码

        定义一个结构体BlockQueues用于封装计算任务的阻塞队列和存储任务的阻塞队列;

        生产者线程执行productor函数域中的代码,用于生产运算任务对象CalTask t,并将其push进计算任务的阻塞队列中。

        消费者线程执行consumer函数域中的代码,用于从任务队列中获取CalTask t,并通过CalTask类中的“仿函数”对结果进行计算,同时消费者线程还要将计算结果和Save方法生成一个SaveTask对象,将其存储于存储阻塞队列save_bq中;

        存储线程执行saver函数域中的代码,通过拿到存储任务的阻塞队列save_bq,通过operator()间接调用Save方法,将数据结果追加式存储于文本文件中。

2.1BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
const int gMaxCap=5;
template <class T>
class BlockQueue
{
public:BlockQueue(const int capacity=gMaxCap):_capacity(capacity){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_pcond,nullptr);pthread_cond_init(&_ccond,nullptr);}void push(const T& in)//输入型参数:const &{pthread_mutex_lock(&_mutex);//细节2:充当条件的判断必须是while,不能用if//这是因为唤醒的时候存在唤醒异常或伪唤醒的情况//需要让线程重新使用IsFull对空间就行判断,确保100%唤醒while(IsFull()){//细节1://该线程被pthread_cond_wait函数挂起后,会自动释放锁。//该线程被pthread_cond_signal函数唤醒后,会自动重新获取原来那把锁pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足,无法生产,此时我们的生产者进行等待}//走到这里一定没有满_q.push(in);//刚push了一个数据,可以试着消费者把他取出来(唤醒消费者)//细节3:pthread_cond_signal()这个函数,可以放在临界区内部,也可以放在临界区外部pthread_cond_signal(&_ccond);//可以设置水位线,满多少就唤醒消费者pthread_mutex_unlock(&_mutex);//pthread_cond_signal(&_ccond);//也可以放在解锁之后}void pop(T* out)//输出型参数:*  //输入输出型:&{pthread_mutex_lock(&_mutex);while(IsEmpty()){pthread_cond_wait(&_ccond,&_mutex);//消费者休眠}//先把数据处理好,再唤醒消费者*out=_q.front();_q.pop();//走到这里,一定能保证不为空,唤醒生产者进行生产pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}
private:bool IsEmpty(){return _q.empty();}bool IsFull(){return _q.size()==_capacity;}
private:std::queue<T> _q;int _capacity;//阻塞队列的容量pthread_mutex_t _mutex;//互斥锁pthread_cond_t _pcond;//生产者对应的条件变量pthread_cond_t _ccond;//消费者对应的条件变量
};

2.2Task.hpp

#pragma once
#include <iostream>
#include <functional>
#include <string>
class CalTask
{//using func=std::function<int(int,int,char)>;typedef std::function<int(int,int,char)> func_t;//函数对象
public:CalTask(){}CalTask(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callBack(func){}std::string operator()()//消费者调用{int result=_callBack(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=%d",_x,_op,_y,result);//结果字符串return buffer;}std::string toTaskString()//生产者调用{char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;//加减乘除取模func_t _callBack;//回调函数
};const std::string oper = "+-*/%";int myMath(int x, int y, char op){int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero" << std::endl;result = -1;}elseresult = x % y;}break;default:break;}return result;}
class SaveTask
{typedef std::function<void(const std::string& )> func_t;
public:SaveTask(){}SaveTask(const std::string& message,func_t func):_message(message),_func(func){}void operator()(){_func(_message);//Main.cc传入的result}
private:std::string _message;func_t _func;
};
void Save(const std::string& message)
{const std::string target="./log.txt";FILE* fp=fopen(target.c_str(),"a+");if(fp==nullptr){std::cerr<<"fopen error"<<std::endl;return;}fputs(message.c_str(),fp);fputs("\\n",fp);fclose(fp);
}

2.3Main.cc

#include <iostream>
#include <queue>
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
//阻塞队列类。 C:计算任务,S:存储任务
template <class C, class S>
struct BlockQueues
{BlockQueue<C> *c_bq;BlockQueue<S> *s_bq;
};
// 生产者跑这个函数,参与生产任务
void *productor(void *args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->c_bq;//计算任务while (1){// 生产数据int x = rand() % 10 + 1;int y = rand() % 5;int operCode = rand() % oper.size();CalTask t(x, y, oper[operCode], myMath);bq->push(t); // 把任务push进阻塞队列printf("Productor thread,生产计算任务:%s\\n", t.toTaskString().c_str());sleep(1);}return nullptr;
}
// 消费者跑这个函数,参与计算任务和存储任务
void *consumer(void *args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->c_bq;//计算任务BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->s_bq;//存储任务while (1){// 消费数据,计算任务CalTask t;bq->pop(&t);std::string result=t();printf("Cal thread,完成计算任务:%s...done\\n", result.c_str());//存储任务SaveTask save(result,Save);save_bq->push(save);//把save对象push进储存阻塞队列中printf("Cal thread,推送存储任务完成\\n");//sleep(1);}return nullptr;
}//储存线程跑这个函数,参与存储任务
void* saver(void* args)
{BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->s_bq;//拿到存储任务while(1){SaveTask t;save_bq->pop(&t);t();//调用消费者类中的Save方法printf("save thread,保存任务完成...\\n");}return nullptr;
}int main()
{srand((unsigned int)time(nullptr) ^ getpid());BlockQueues<CalTask, SaveTask> bqs;// 两个阻塞队列bqs.c_bq = new BlockQueue<CalTask>();bqs.s_bq = new BlockQueue<SaveTask>();pthread_t c, p,s;//消费者、生产者、保存者线程pthread_create(&p, nullptr, productor, &bqs);pthread_create(&c, nullptr, consumer, &bqs);pthread_create(&s, nullptr,saver ,&bqs);pthread_join(c, nullptr);pthread_join(p, nullptr);pthread_join(s, nullptr);delete bqs.c_bq;delete bqs.s_bq;return 0;
}

3、基于生产者消费者模型的多生产者多消费者的多线程代码

#include <iostream>
#include <queue>
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
//阻塞队列类。 C:计算任务,S:存储任务
template <class C, class S>
struct BlockQueues
{BlockQueue<C> *c_bq;BlockQueue<S> *s_bq;
};
// 生产者跑这个函数,参与生产任务
void *productor(void *args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->c_bq;//计算任务while (1){// 生产数据int x = rand() % 10 + 1;int y = rand() % 5;int operCode = rand() % oper.size();CalTask t(x, y, oper[operCode], myMath);bq->push(t); // 把任务push进阻塞队列printf("Productor thread,生产计算任务:%s\\n", t.toTaskString().c_str());sleep(1);}return nullptr;
}
// 消费者跑这个函数,参与计算任务和存储任务
void *consumer(void *args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->c_bq;//计算任务//BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->s_bq;//存储任务while (1){// 消费数据,计算任务CalTask t;bq->pop(&t);std::string result=t();printf("Cal thread,完成计算任务:%s...done\\n", result.c_str());}return nullptr;
}//储存线程跑这个函数,参与存储任务
void* saver(void* args)
{BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(args))->s_bq;//拿到存储任务while(1){SaveTask t;save_bq->pop(&t);t();//调用消费者类中的Save方法printf("save thread,保存任务完成...\\n");}return nullptr;
}int main()
{srand((unsigned int)time(nullptr) ^ getpid());BlockQueues<CalTask, SaveTask> bqs;// 两个阻塞队列bqs.c_bq = new BlockQueue<CalTask>();bqs.s_bq = new BlockQueue<SaveTask>();pthread_t c[2], p[3],s;//消费者、生产者、保存者线程pthread_create(c, nullptr, consumer, &bqs);pthread_create(c+1, nullptr, consumer, &bqs);pthread_create(p, nullptr, productor, &bqs);pthread_create(p+1, nullptr, productor, &bqs);pthread_create(p+2, nullptr, productor, &bqs);//pthread_create(&s, nullptr,saver ,&bqs);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);//pthread_join(s, nullptr);delete bqs.c_bq;delete bqs.s_bq;return 0;
}

        对Main.cc进行改造,另外两个头文件不变。

        该代码有多个生产者和消费者,通过BlockQueue.hpp中的BlockQueue类实例化出的对象中的push和pop方法,让各生产者和消费者共同去竞争同一把锁,保证在同一时间,只有一个线程抢到锁并执行对应的任务。

4、生产者消费者模型真的高效吗?

        从本段第三节我们看到,大量的生产者、消费者全部在虎视眈眈的争夺同一把锁,也就是说,一次只能放一个线程去阻塞队列中完成任务,那效率不是非常慢?既然如此为何要采用这种模型?

        因为传统的线程运作方式会让大部分线程阻塞在临界区之外,而生产者消费者模型则是将任务的工序拆开,一组线程分为生产者,另一组分为消费者。充分利用了生产者的阻塞时间,用以提前准备好生产资源;同时也利用了消费者计算耗时的问题,让消费者线程将更多的时间花在计算上,而不是抢不到锁造成线程“干等”。

        生产者消费者模型可以在生产前和消费后,让线程并行执行,减少线程阻塞时间。

三、基于环形队列的多生产者多消费者模型(信号量控制同步与互斥)

        环形队列本质是一个数组模拟的首尾相连的队列。

        在环形队列中,只有队列全空或者全满时,生产者和消费者才会站在队列的同一格里。当环形队列全空时,必须生产者线程先生产;当环形队列为满时,必须消费者线程先生产。在其他情况下,生产者和消费者线程可以并发执行,只有在队列为空或满的时候,才有同步与互斥问题。

        我们可以给生产者定义一个信号量,用以表示剩余空间资源;给消费者定义一个信号量,用以表示数据资源。

void Push(const T& in)//向环形队列中push数据
{P(_spaceSem);//调用sem_wait,信号量申请成功,未来一定能访问到临界资源pthread_mutex_lock(&_pmutex);_queue[_productorStep++]=in;_productorStep%=_cap;pthread_mutex_unlock(&_pmutex);V(_dataSem);//生产完毕后,调用V操作,让消费者信号量++。
}

        环形队列中最少有一个线程行动(为空或为满);最多有两个线程行动(生产消费各一个)。那么多生产者多消费者的意义是什么?

        这个意义和上面讲的生产者消费者模型一样,线程在资源生产和消费的过程可能巨花时间,该方式可以让线程利用原先被阻塞的时间,用以生产和消费活动,提升总体效率。

1、RingQueue.hpp

#pragma once
#include <iostream>
#include <semaphore.h>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <pthread.h>
static const int gcap=5;
template <class T>
class RingQueue
{
private:void P(sem_t& spaceSem){int n=sem_wait(&spaceSem);assert(0==n);//这里最好if(void)n;}void V(sem_t& dataSem){int n=sem_post(&dataSem);assert(0==n);//这里最好if(void)n;}
public:RingQueue(const int& cap=gcap):_queue(cap)//可以这样初始化vector吗?,_cap(cap),_productorStep(0),_consumerStep(0){int n=sem_init(&_spaceSem,0,_cap);assert(0==n);n=sem_init(&_dataSem,0,0);assert(0==n);pthread_mutex_init(&_pmutex,nullptr);pthread_mutex_init(&_cmutex,nullptr);}//生产者调用Pushvoid Push(const T& in)//向环形队列中push数据{P(_spaceSem);//调用sem_wait,信号量申请成功,未来一定能访问到临界资源pthread_mutex_lock(&_pmutex);_queue[_productorStep++]=in;_productorStep%=_cap;pthread_mutex_unlock(&_pmutex);V(_dataSem);//生产完毕后,调用V操作,让消费者信号量++。}//消费者调用Popvoid Pop(T* out)//向环形队列中pop数据,out是输出型参数{P(_dataSem);//确认消费者信号量是否可以--,不能减就阻塞pthread_mutex_lock(&_cmutex);*out=_queue[_consumerStep++];_consumerStep%=_cap;pthread_mutex_unlock(&_cmutex);V(_spaceSem);//消费完毕后,生产者信号量++}~RingQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);pthread_mutex_destroy(&_pmutex);pthread_mutex_destroy(&_cmutex);}
private:std::vector<T> _queue;//vector模拟队列int _cap;//队列的最大容量sem_t _spaceSem;//生产者信号量,表明环形队列中剩余空间资源数量sem_t _dataSem;//消费者信号量,表明环形队列中存在的数据资源数量int _productorStep;//生产者在循环列表的脚步int _consumerStep;//消费者在循环列表的脚步pthread_mutex_t _pmutex;//生产者锁pthread_mutex_t _cmutex;//消费者锁
};

2、Task.hpp 

#pragma once
#include <iostream>
#include <functional>
#include <string>
class Task
{//using func=std::function<int(int,int,char)>;typedef std::function<int(int,int,char)> func_t;//函数对象
public:Task(){}Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callBack(func){}std::string operator()()//消费者调用{int result=_callBack(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=%d",_x,_op,_y,result);//结果字符串return buffer;}std::string toTaskString()//生产者调用{char buffer[1024];snprintf(buffer,sizeof(buffer),"%d %c %d=?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;//加减乘除取模func_t _callBack;//回调函数
};const std::string oper = "+-*/%";
int myMath(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero" << std::endl;result = -1;}elseresult = x % y;}break;default:break;}return result;
}

3、main.cc

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include "RingQueue.hpp"
#include "Task.hpp"
std::string SelfName()
{char name[128];snprintf(name,sizeof(name),"thread:0X%x",pthread_self());return name;
}
void* ProductorRoutine(void* product)
{RingQueue<Task>* rqueue=static_cast<RingQueue<Task>*>(product);while(1){//模拟构建一个任务int x=rand()%1000;int y=rand()%2000;char op=oper[rand()%oper.size()];Task t(x,y,op,myMath);//构建任务对象rqueue->Push(t);std::cout<<SelfName()<<"生产者生产任务成功:"<<t.toTaskString()<<std::endl;sleep(1);}
}
void* ConsumerRoutine(void* consume)
{RingQueue<Task>* rqueue=static_cast<RingQueue<Task>*>(consume);while(1){Task t;rqueue->Pop(&t);std::cout<<SelfName()<<"消费者消费任务成功"<<t()<<std::endl;}
}
int main()
{srand((unsigned int)time(nullptr)^getpid()^pthread_self());RingQueue<Task>* rq=new RingQueue<Task>();pthread_t p[4],c[7];//定义生产者消费者线程for(int i=0;i<4;++i)pthread_create(p+i,nullptr,ProductorRoutine,rq);for(int i=0;i<7;++i)pthread_create(c+i,nullptr,ConsumerRoutine,rq);for(int i=0;i<4;++i)pthread_join(p[i],nullptr);for(int i=0;i<7;++i)pthread_join(c[i],nullptr);return 0;
}

漂亮主播大全