std :: packaged_task如何工作

时间:2017-10-10 08:29:13

标签: c++ multithreading c++11 future packaged-task

我正在分析以下代码片段,并试图详细了解它:

template<typename FUNCTION, typename... ARGUMENTS>
auto ThreadPool::add( FUNCTION&& Function, ARGUMENTS&&... Arguments ) -> std::future<typename std::result_of<FUNCTION(ARGUMENTS...)>::type>
{
    using PackedTask = std::packaged_task<typename std::result_of<FUNCTION(ARGUMENTS...)>::type()>;

    auto task = std::make_shared<PackedTask>(std::bind(std::forward<FUNCTION>(Function), std::forward<ARGUMENTS>(Arguments)...));

    // get the future to return later
    auto ret = task->get_future();

    {
        std::lock_guard<std::mutex> lock{jobsMutex};
        jobs.emplace([task]() { (*task)(); });
    }

    // let a waiting thread know there is an available job
    jobsAvailable.notify_one();

    return ret;
}

关于std::packaged_task我几乎没有问题。

正如您在add(...)方法体中所看到的,std::packaged_task - task的实例是局部变量,其范围以方法执行结束而结束。 ret类型的返回值std::future由副本返回。 ret的值来自task(本地)。所以一旦方法执行完成,task超出了范围,所以我希望返回的连接的std :: future实例变得无效,我的理解是否正确?

在执行方法期间,要在线程中执行的任务方法被置于std::queue<Job> jobs。为什么只有指向operator()的{​​{1}}的指针保存std::packaged_task中作为方法参数给出的Function的引用?我希望直接存储std::queue以保存对正在创建的实例的引用...?

无论如何,源代码片段来自ThreadPool实现,可以在https://github.com/dabbertorres/ThreadPool找到并且似乎完全正常。所以我认为这是正确的,但遗憾的是我并不完全清楚...如果有人能解释这些东西是如何工作的话,我会很高兴...

非常感谢愿意提供帮助的任何人。干杯马丁

3 个答案:

答案 0 :(得分:3)

  

正如您在add(...)方法体中看到的那样,实例   std :: packaged_task - task是局部变量,其范围以   方法执行结束。

是的,它是std::shared_ptr<PackedTask>的局部变量。当它超出范围时,它将引用计数减1.如果新引用计数为0,它将删除它指向的对象。幸运的是,jobs拥有该共享指针的副本,因此指向对象保持活动状态。

  

std :: future类型的返回值ret由副本返回。

不完全是。 移动而不是通过复制返回ret的可能性更大。

  

任务超出范围,所以我期待连接的std :: future   被返回的实例变得无效,我的理解是否正确?

同样,task是一个共享指针。 jobs队列使其保持活动状态,并且可能ret保存该共享指针的另一个副本(以实际提取task的结果)。所以只要任务在队列中并且某人将未来留给该结果,任何事情都不会变得无效。

  

我希望直接存储std :: packaged_task以便   保持对正在创建的实例的引用...?

是的,它可以直接存储std::packaged_task。我的猜测是这个图书馆的作者并不想搞乱不可操作/不可移动的仿函数。如果std::bind的结果不可复制不可移动,则无法将std::packaged_task真正移至队列中。使用共享指针解决了这个问题,因为你不复制/移动packaged_task本身,只有指针。但是,您可以将任务对象直接构建到队列中,但是在保持锁定下执行它并不是一个好习惯。

我确实同意,将task移动到lambda中比复制它更有效。

答案 1 :(得分:1)

  

所以一旦方法执行完成,任务超出范围,所以我希望连接的std :: future实例返回无效,我的理解是否正确?

task是一个std::shared_ptr,被复制到传递给jobs的闭包中 - 只要作业存在,它就会保持活着。

答案 2 :(得分:0)

  

所以一旦方法执行完成,任务超出范围,所以我希望连接的std :: future实例返回无效,我的理解是否正确?

没有

future是共享状态的句柄。这种状态由承诺和未来共享。

将其视为将一种特殊的shared_ptr封装到管理承诺/未来关系状态的(不透明)对象中。