从另一个任务创建任务 - 用线程池库替换std :: async时出现死锁

时间:2017-12-13 03:39:09

标签: c++ multithreading asynchronous c++14 deadlock

这是一个很好的工作多线程代码。它使用std::async: -

enter image description here

class C{  public: int d=35;  };
class B{
    public: C* c;
    public: void test(){
        std::vector<std::future<void>> cac;
        for(int n=0;n<5;n++){
            cac.push_back(
                std::async(std::launch::async,[&](){  
                    test2();
                })
            );
        }
        for(auto& ele : cac){
            ele.get();
        }
    };
    public: void test2(){
        std::vector<std::future<void>> cac;
        for(int n=0;n<5;n++){
            cac.push_back(
                 std::async(std::launch::async,[&](){  
                    int accu=0;
                    for(int i=0;i<10000;i++){
                        accu+=i;
                    }
                    std::cout<<accu<<" access c="<<c->d<<std::endl;
                })
            );
        }
        for(auto& ele : cac){
            ele.get();
        }
    }
};

以下是测试用例: -

int main(){
    C c;
    B b; b.c=&c;
    b.test();
    std::cout<<"end"<<std::endl;
}

它有效,但如果我从std::async更改为使用线程池库,例如

......我会遇到访问冲突或冻结(可能是死锁)。

问题

这是否意味着我无法从其他任务创建任务? 哪部分代码是限制的原因?如何克服它?

以下是3 MCVE std::async (Coliru)Progschj's ThreadPool (pastebin)ctpl (pastebin)

我试图深入他们的图书馆,但凭借我有限的经验,我无法找到原因。

线索

在实际情况中,当任务量> 1时,错误趋于发生。螺纹量(4)。
有时,它会导致无关库的线程永远停止。 (例如SDL键盘监听器。)

在更复杂的程序中,Visual Studio有时会捕获this (B*) = 0x02 (我想在a-lambda-with-capture在循环中使用一次后会删除this的引用;超出范围??)

以下是 ThreadPool 中最可疑的位置(两个库非常相似): -

// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) 
    -> std::future<typename std::result_of<F(Args...)>::type>
{
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );

    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        // don't allow enqueueing after stopping the pool
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}

2 个答案:

答案 0 :(得分:2)

死锁是因为ThreadPool中的任务无法被抢占。因此,如果您以递归方式调用ThreadPool::enqueue方法,然后等待结果,则会导致死锁,因为所有线程都已使用,无法执行新排队的任务。

更详细一点:
让我们一步一步地完成你的代码 1.你调用B::test()这个函数在线程池中包含5个任务,然后在ele.get()中等待他们的结果,即完成它们。 2.线程池中的线程deque其中一个任务(在步骤1中引用),这意味着如果线程数量<=任务量,则所有线程都执行B::test2()。在这里,您再次在线程池中排队5个新任务 现在到了关键点。稍后在B::test2()中,您等待ele.get()这些任务的结果,这意味着线程池的线程被阻塞,直到执行了任务(带有for循环的任务)并且它们的结果已保存在std::future中。但是由于线程池的线程被阻塞,它们不能再执行任务了。因此,当前正在运行的任务等待执行其他任务,这些任务将永远不会被执行,因为所有线程都被阻止==&gt;僵局。

答案 1 :(得分:1)

迟到的答案,但我希望它有用。前段时间我开发了一个小而有效的threadpool library。 经过很少的修改,我已经测试了你的用例,它似乎工作得很好,所以也许你可以看一下。

Live demo。 (对不起,我必须在里面包含所有的库代码,用你的例子来测试它)