Multithreading C+;中的通用多生产者/消费者+;11(或以上)

Multithreading C+;中的通用多生产者/消费者+;11(或以上),multithreading,c++11,producer-consumer,Multithreading,C++11,Producer Consumer,我使用C++11(或更高版本)多线程编写了一个“通用”多生产者/消费者。下面的代码可以正常工作,但是如果创建了太多的生产者/消费者线程,代码就会挂起/崩溃 其思想是将关注点巧妙地分开:MultiProducerConsumer对象负责协议(线程维护、互斥、condvar),而“用户”将执行具体工作的相关函子(生产者、消费者、终止谓词)注入到对象中 使用VS 2017和cygwin g++进行测试。cygwin的情况更糟(为什么?)。我不知道问题出在哪里,我需要一个提示。提前谢谢 标题multi_

我使用C++11(或更高版本)多线程编写了一个“通用”多生产者/消费者。下面的代码可以正常工作,但是如果创建了太多的生产者/消费者线程,代码就会挂起/崩溃

其思想是将关注点巧妙地分开:MultiProducerConsumer对象负责协议(线程维护、互斥、condvar),而“用户”将执行具体工作的相关函子(生产者、消费者、终止谓词)注入到对象中

使用VS 2017和cygwin g++进行测试。cygwin的情况更糟(为什么?)。我不知道问题出在哪里,我需要一个提示。提前谢谢

标题multi_producer_consumer.hpp:

#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;