C++ 一个生产者线程,几个消费者
我有一个生产者线程和几个消费者,每个消费者都有:拥有数据和唯一id的自己的队列。 我使用std::map来标识线程的每个队列C++ 一个生产者线程,几个消费者,c++,multithreading,c++11,C++,Multithreading,C++11,我有一个生产者线程和几个消费者,每个消费者都有:拥有数据和唯一id的自己的队列。 我使用std::map来标识线程的每个队列 typedef std::map<int, std::queue<Task>> TaskMap; TaskMap inputQueue; TaskMap outputQueue; typedef std::map TaskMap; 任务映射输入队列; 任务映射输出队列; 每个使用者线程处理其队列中的数据,若队列为空,则线程必须等待数据。 如果我
typedef std::map<int, std::queue<Task>> TaskMap;
TaskMap inputQueue;
TaskMap outputQueue;
typedef std::map TaskMap;
任务映射输入队列;
任务映射输出队列;
每个使用者线程处理其队列中的数据,若队列为空,则线程必须等待数据。
如果我只想使用一个线程,我可以使用std::condition_变量和std::unique_锁,但我有几个使用者,所以我需要几个std::condition_变量,但我无法将它们保存在容器中(复制/分配被删除)。
所以我使用这样的代码
while(q.empty()) {
std::cout << "waiting...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
while(q.empty()){
std::coutOnestd::conditional_变量
和Onestd::mutex
应该足够了
Task t;
{
std::unique_lock<std::mutex> lock(mtx);
while (q.empty())
cond_var.wait(lock);
t = std::move(q.front());
q.pop_front();
}
任务t;
{
std::唯一锁(mtx);
while(q.empty())
条件变量等待(锁定);
t=std::move(q.front());
q、 pop_front();
}
和主线程将做
{
std::lock_guard<std::mutex> lock(mtx);
q.emplace_front(/*...*/);
cond_var.notify_all();
}
{
标准:锁和防护锁(mtx);
q、 炮位前方(/*…*/);
cond_var.notify_all();
}
主线程将唤醒所有线程,但大多数线程将返回睡眠状态,因为它们的队列仍然是空的。因为每个使用者都有自己的队列,并且所有使用者只有一个生产者,所以本质上是一个生产者一个消费者的场景
换句话说,您没有在所有使用者之间共享单个队列。调用OOP以执行此任务:
class ConcurentTaskQueue{
std::mutex lock;
std::condition_variable m_ConditionVariable;
std::queue<Task> m_TaskQueue;
public:
Task getTask(){
std::unique_lock<std::mutex> synchLock (lock); //NOTE: consider doing this with a while(programIsRunning){} loop
while(m_TaskQueue.empty()){
m_ConditionVariable.wait(synchLock);
}
Task task(std::move(m_TaskQueue.front()));
m_TaskQueue.pop();
return task;
}
void addTask (Task task){
std::unique_lock<std::mutex> synchLock (lock);
m_TaskQueue.push(std::move(task));
m_ConditionVariable.notify_one();
}
};
类ConcurentTaskQueue{
std::互斥锁;
std::条件变量m\U条件变量;
std::队列m_TaskQueue;
公众:
任务getTask(){
STD::UnQueQueLoad同步锁(锁);/Note:考虑用一段时间(程序运行){}循环来做这件事
while(m_TaskQueue.empty()){
m_ConditionVariable.wait(synchLock);
}
任务(std::move(m_TaskQueue.front());
m_TaskQueue.pop();
返回任务;
}
void addTask(任务任务){
std::唯一锁定同步锁定(锁定);
m_TaskQueue.push(std::move(task));
m_ConditionVariable.notify_one();
}
};
现在简单地说:
std::map<size_t,ConcurentTaskQueue> inputQueue;
std::thread producer ([&]{
Task task = produceTask()
inputQueue[ID].addTask(task);
});
std::thread consumer1([&]{
Task task = inputQueue[ID].getTask();
});
std::thread consumer2([&]{
Task task = inputQueue[ID].getTask();
});
std::映射输入队列;
线程生成器([&]{
任务任务=生产任务()
inputQueue[ID].addTask(任务);
});
std::线程使用者1([&]{
Task Task=inputQueue[ID]。getTask();
});
std::线程使用者2([&]{
Task Task=inputQueue[ID]。getTask();
});
编辑2:
使用线程池如果您这样做不是为了研究/学习,而是为了生产代码,我建议您使用众多实现中的一种。正确、高效和可扩展地实现这一点并不容易,但其他人已经解决了问题。有许多选项,例如:
缺少副本/分配并不意味着你不能将东西放入容器。只有部分操作将不可用。你仍然可以使用emplace
和at
。请查看。“但是我有几个消费者,所以我需要几个std::condition\u变量”不正确。那么我如何识别我需要唤醒的线程呢?@user3365834您在这里非常需要一个线程池。不要试图重新发明轮子。但是性能呢?唤醒多个线程是一个沉重的操作,不是吗?所以使用notify_one()
并且只唤醒一个使用者,该使用者将接受任务并处理它。您不能将std::mutex
传递给条件变量::wait()
,并且此示例不是异常安全的。您不应该使用mtx.lock()
和mtx.unlock())
,改用unique\u lock
。@JonathanWakely谢谢你,没有注意到。不,你让事情变得更糟了。不要在线程之间共享unique\u lock
,不要调用lock()
和unlock()
明确地说。我已经为您修复了它。生产者为所有消费者生成数据。在生产者线程中,我有数据和消费者id,所以我将这些数据放在id标识的队列中。@user3365834仍然,只要消费者不使用同一队列从中获取数据,它基本上是单个生产者单个消费者。事实上,没有“不止一个消费者,而且您的生产者为多个消费者扮演单一生产者,这并不能改变线程以单一生产者-单一消费者的方式彼此同步的事实。@Maxim Egorushkin据我所知,一个线程填满队列,多个线程可以使用您的实现的一个队列。”tion不考虑在等待
时出现虚假唤醒,这可能会导致未定义的行为。你是对的,但这只是方向,不是一个完整的实现。那么请在回答中包含你的假设。错误的代码比没有代码更糟糕。你不通过添加原子来解决虚假唤醒,你解决的是虚假唤醒在这种情况下,它应该是while(m_TaskQueue.empty())
而不是if(m_TaskQueue.empty())
返回std::move(task);
是错误的,它阻止了RVO,返回任务;
仍然可以使用移动构造函数,但也允许RVO。考虑到在这里的答案中建议的糟糕的坏代码,我倾向于同意。