Multithreading C+;中的通用多生产者/消费者+;11(或以上)
我使用C++11(或更高版本)多线程编写了一个“通用”多生产者/消费者。下面的代码可以正常工作,但是如果创建了太多的生产者/消费者线程,代码就会挂起/崩溃 其思想是将关注点巧妙地分开:MultiProducerConsumer对象负责协议(线程维护、互斥、condvar),而“用户”将执行具体工作的相关函子(生产者、消费者、终止谓词)注入到对象中 使用VS 2017和cygwin g++进行测试。cygwin的情况更糟(为什么?)。我不知道问题出在哪里,我需要一个提示。提前谢谢 标题multi_producer_consumer.hpp:Multithreading C+;中的通用多生产者/消费者+;11(或以上),multithreading,c++11,producer-consumer,Multithreading,C++11,Producer Consumer,我使用C++11(或更高版本)多线程编写了一个“通用”多生产者/消费者。下面的代码可以正常工作,但是如果创建了太多的生产者/消费者线程,代码就会挂起/崩溃 其思想是将关注点巧妙地分开:MultiProducerConsumer对象负责协议(线程维护、互斥、condvar),而“用户”将执行具体工作的相关函子(生产者、消费者、终止谓词)注入到对象中 使用VS 2017和cygwin g++进行测试。cygwin的情况更糟(为什么?)。我不知道问题出在哪里,我需要一个提示。提前谢谢 标题multi_
#pragma once
#include <algorithm>
#include <functional>
#include <iterator>
#include <thread>
#include <mutex>
#include <condition_variable>
//#include <cassert>
template<typename Container>
struct MultiProducerConsumer
{
using Type = typename Container::value_type;
using ModifierFct = std::function<void(Container&)>;
using DoneFctr = std::function<bool(const Container&)>;
MultiProducerConsumer(const Container& q,
ModifierFct producer,
ModifierFct consumer,
DoneFctr donef,
size_t n_producers,
size_t n_consumers):
m_queue(q),
m_pf(producer),
m_cf(consumer),
m_producers(n_producers),
m_consumers(n_consumers),
m_done(donef),
m_joined(false)
{
///std::lock_guard<std::mutex> lk(m_mutex);//why? to prevent the producers to start before consumers are created. So what, if they do?
for (auto i = 0; i < n_producers; ++i)
{
m_producers[i] = std::thread(std::mem_fn(&MultiProducerConsumer::produce), this, i);
}
for (int i = 0; i < n_consumers; ++i)
{
m_consumers[i] = std::thread(std::mem_fn(&MultiProducerConsumer::consume), this, i);
}
}
virtual ~MultiProducerConsumer(void)
{
if (!m_joined)
join();
}
virtual bool done(void) const
{
std::lock_guard<std::mutex> lk(m_mutex);
return m_done(m_queue);
}
void join(void)
{
std::for_each(m_producers.begin(), m_producers.end(), std::mem_fn(&std::thread::join));
std::for_each(m_consumers.begin(), m_consumers.end(), std::mem_fn(&std::thread::join));
m_joined = true;
}
protected:
virtual void produce(size_t i)
{
while (!done())
{
std::lock_guard<std::mutex> lk(m_mutex);
m_pf(m_queue);
///if (i == 0)//should only only one thread notify all the consumers...? nope
m_condvar.notify_all();//notifies all...not one
}
}
virtual void consume(size_t i)
{
while (!done())
{
std::unique_lock<std::mutex> lk(m_mutex);
m_condvar.wait(lk, [this]() {
return !m_queue.empty();
});
m_cf(m_queue);
}
}
private:
Container m_queue;
ModifierFct m_pf;
ModifierFct m_cf;
DoneFctr m_done;
mutable std::mutex m_mutex;
std::condition_variable m_condvar;
std::vector<std::thread> m_producers;
std::vector<std::thread> m_consumers;
bool m_joined;
};
#pragma一次
#包括
#包括
#包括
#包括
#包括
#包括
//#包括
模板
结构多产品消费者
{
使用Type=typename容器::value\u类型;
使用ModifierFct=std::函数;
使用DoneFctr=std::function;
多产品消费者(集装箱和q,
修改器CT制作人,
修改器CT消费者,
DoneFctr donef,
生产商的规模,
尺寸(不包括消费者):
m_队列(q),
m_pf(制作人),
m_cf(消费者),
m_生产者(n_生产者),
m_消费者(n_消费者),
m_done(donef),
m_已加入(错误)
{
///std::lock_guard lk(m_mutex);//为什么?为了防止生产者在消费者创建之前启动。那么,如果他们这样做了呢?
对于(自动i=0;i
下面的测试人员使用“生成”的向量队列(只需从“外部”队列矩阵移动到生产者/消费者队列)。消费者通过对每个向量求和并将求和存储到另一个“外部”容器(sums)中来“消费”向量。当遇到第一个向量求和为零时,整个过程终止。代码如下:
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <queue>
#include <numeric>
#include <iterator>
#include <cassert>
#include "multi_producer_consumer.hpp"
template<typename T>
using QVec = std::queue<std::vector<T>>;
template<typename T>
inline
T sum(const std::vector<T>& v)
{
return std::accumulate(v.begin(), v.end(), 0);
}
template<typename T>
T from_string(std::string&& str)
{
T ret;
std::stringstream ss(str);
ss >> ret;
return ret;
}
int main(int argc, char* argv[])
{
int n_p = 1;
int n_c = 1;
if (argc == 3)
{
n_p = from_string<int>(argv[1]);
n_c = from_string<int>(argv[2]);
}
const unsigned long max_n_threads = std::thread::hardware_concurrency();
std::cout << "max # threads: " << max_n_threads << "\n";
std::cout << "n_producers: " << n_p << ", n_consumers: " << n_c << "\n";
try {
std::vector<int> vstart(1, 1);
std::vector<int> vstop(1, 0);
std::queue<std::vector<int>> matrix;
matrix.push(vstart);
matrix.push(std::vector<int>{ 1, 2, 3, 4, 5 });
matrix.push(std::vector<int>{ 6, 7, 8, 9 });
matrix.push(std::vector<int>{ 10, 11, 12, 13 });
matrix.push(vstop);
matrix.push(std::vector<int>{ 20, 21, 22, 23 });//testing: this shouldn't get processed: okay, it's not
std::vector<long> sums;
QVec<int> qqv;
//multi-producer-consumer that feeds vector from a queue
//to a consumer that sums them up, until sum is zero:
//
MultiProducerConsumer<QVec<int>> mpc(qqv,
[&matrix](QVec<int>& qv) { //producer function: move elements from matrix into qv
if (!matrix.empty())
{
auto v = matrix.front();
matrix.pop();
qv.push(v);
}
},
[&sums](QVec<int>& qv) { //consumer function: pop from qv and sum up elements
//if (!qv.empty())//this test is superfluous
//{
auto v = qv.front();
qv.pop();
sums.push_back(sum(v));
//}
},
[](const QVec<int>& qv) { //done predicate: if nonempty top of queue sums up to 0: done; else not done;
if (!qv.empty())
{
auto v = qv.front();
return (sum(v) == 0);
}
return false;
}, n_p, n_c);//1,1 => okay; 1,2 => okay; 2,2 => okay; 5,5 => okay on Win64; hangs on cygwin; 5,10 => it can hang
//need main thread to block until producers/consumers are done,
//so that matrix/sums are not destructed while
//producers/consumers are still trying to use them:
//
mpc.join();
std::cout << "sums:\n";
std::copy(std::begin(sums), std::end(sums), std::ostream_iterator<int>(std::cout, "\n"));
}
catch (std::exception& ex)
{
std::cerr << ex.what() << "\n";
return 1;
}
catch (...)
{
std::cerr << "Unknown exception.\n";
return 1;
}
std::cout << "Done!" << std::endl;
return 0;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括“多生产者消费者.hpp”
模板
使用QVec=std::queue;
模板
内联
T和(常数标准::向量和v)
{
返回std::累计(v.begin(),v.end(),0);
}
模板
T来自_字符串(std::string&&str)
{
T-ret;
std::stringstream ss(str);
ss>>ret;
返回ret;
}
int main(int argc,char*argv[])
{
int n_p=1;
int n_c=1;
如果(argc==3)
{
n_p=来自_字符串(argv[1]);
n_c=来自_字符串(argv[2]);
}
const unsigned long max_n_threads=std::thread::hardware_concurrency();
std::cout--第1/2部分:为什么它不工作的答案---
matrix.push(vstop);
matrix.push(std::vector<int>{ 20, 21, 22, 23 });//testing: this shouldn't get processed: okay, it's not
matrix.push(vstop);
push(std::vector{20,21,22,23})//测试:这不应该被处理:好的,它不是
是的(有时)。我发现当它挂起时,是因为制作人把最后一件东西吸了进去
广义生产者和消费者函数中存在缺陷。以生产者为例:
virtual bool done(void) const
{
std::lock_guard<std::mutex> lk(m_mutex);
return m_done(m_queue);
}
virtual void produce(size_t i)
{
while (!done()) // <---- HERE to...
{
std::lock_guard<std::mutex> lk(m_mutex); // <----- ...HERE. done() condition may not hold, as mutex was released
m_pf(m_queue);consumers...? nope
m_condvar.notify_all();
}
}
virtualbool done(void)const
{
std::lock_guard lk(mutex);
返回m_done(m_队列);
}
虚拟空洞生成(大小\u t i)
{
while(!done())//谢谢。所有要点都是正确的。我将在下面提供一个更正版本,该版本还试图通过更改生产者、消费者和done函子执行其工作的粒度来解决第2部分(“严重的设计缺陷”)。
matrix.push(vstop);
matrix.push(std::vector<int>{ 20, 21, 22, 23 });//testing: this shouldn't get processed: okay, it's not
virtual bool done(void) const
{
std::lock_guard<std::mutex> lk(m_mutex);
return m_done(m_queue);
}
virtual void produce(size_t i)
{
while (!done()) // <---- HERE to...
{
std::lock_guard<std::mutex> lk(m_mutex); // <----- ...HERE. done() condition may not hold, as mutex was released
m_pf(m_queue);consumers...? nope
m_condvar.notify_all();
}
}
[](const QVec<int>& qv) { //done predicate: if nonempty top of queue sums up to 0: done; else not done;
if (!qv.empty())
{
auto v = qv.front();
return (sum(v) == 0);
}
return false;
}
// be careful with the virtual functions + overloading
virtual bool done(std::lock_guard<std::mutex>&) const
{
return m_done(m_queue);
}
virtual bool done(std::unique_lock<std::mutex>&) const
{
return m_done(m_queue);
}
virtual void produce(size_t i)
{
while (true) // 1
{
std::lock_guard<std::mutex> lk(m_mutex);
if (done(lk)) // 2
break;
m_pf(m_queue);
m_condvar.notify_all();
}
m_condvar.notify_all(); // 3. need to break any sleeping consumers
}
virtual void consume(size_t i)
{
while (true) // 1
{
std::unique_lock<std::mutex> lk(m_mutex);
m_condvar.wait(lk, [this]() {
return !m_queue.empty();
});
if (done(lk)) // 2 & 3
break;
m_cf(m_queue);
}
}
[&matrix](const QVec<int>& qv) { // 1
if (!qv.empty())
{
auto v = qv.front();
return (sum(v) == 0);
}
assert(!matrix.empty()); // 2
// or... if (matrix.empty()) throw, since you'll probably want to test in release mode
return false;