C ++通用std :: make_unique,std :: packaged_task和std :: promise问题

时间:2018-09-01 05:06:14

标签: c++ c++17 scheduler

问题 创建调度程序时,功能对象的最后副本或移动是该功能对象(由工作线程)引用的最后位置。如果要使用std :: function将函数存储在调度程序中,则任何std :: promises或std :: packaged_task或其他类似的仅移动类型均无效,因为它们无法被std :: function复制。

类似地,如果要在调度程序中使用std :: packaged_task,则由于许多任务根本不需要打包任务返回的std :: future,因此会带来不必要的开销。

常见但不是很好的解决方案是使用std :: shared_ptr << strong> std :: promise >或std :: shared_ptr << strong> std :: packaged_task >可以,但是会带来很多开销。

解决方案 一个make_owner(类似于make_unique,具有一个关键区别),移动 OR 副本只是转移了销毁对象的控制权。它与std :: unique_ptr基本相同,不同之处在于它是可复制的(即使在复制时,它也总是会移动)。毛....

这意味着移动std :: functions不需要std :: shared_ptr的副本,该副本需要引用计数,并且这也意味着引用计数等方面的开销明显减少。指向该对象的单个原子指针将需要并且移动 OR 副本将转移控制权。主要区别在于副本也可以转移控制权,就严格的语言规则而言,这可能有点不行,但我看不出有其他解决办法。

此解决方案不好,原因是:

  • 它忽略复制共鸣。
  • 它丢弃const(在复制构造函数和operator =中)

Grrr 这不是我想要的解决方案,因此,如果有人知道另一种避免使用共享指针或仅在调度程序中使用packaged_tasks的方法,我很想听听它,因为我很困惑……

我对这种解决方案非常不满意。...有什么想法吗? 我能够通过移动符号来重新实现std :: function,但这似乎给屁股带来了极大的痛苦,并且它在对象生命周期方面也有其自身的问题(但是在将std :: function与引用捕获一起使用时它们已经存在)。 / p>

问题的一些示例:

编辑 请注意,在目标应用程序中,我无法执行std :: thread a(std :: move(a)),因为调度程序线程始终处于运行状态,至多它们处于睡眠状态,从未加入,从未停止过。线程池中有固定数量的线程,我无法为每个任务创建线程。

auto proms = std::make_unique<std::promise<int>>();
auto future = proms->get_future();

std::thread runner(std::move(std::function( [prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
})));

std::cout << future.get() << std::endl;
std::cin.get();

还有一个带有packaged_task的示例

auto pack = std::packaged_task<int(void)>
(   [] 
    {   
        return 1; 
    });
auto future = pack.get_future();

std::thread runner(std::move(std::function( [pack = std::move(pack)]() mutable noexcept
{
    pack();
})));

std::cout << future.get() << std::endl;
std::cin.get();

编辑

我需要从调度程序的上下文中执行此操作,我将无法移至线程。

请注意,以上内容是最低可复制的,std :: async不适合我的应用程序。

1 个答案:

答案 0 :(得分:1)

主要问题是:为什么在传递给std::function构造函数之前要用std::thread包装一个lambda?

这样做非常好:

std::thread runner([prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
});

您可以找到有关为何std::function不允许您存储只能移动的lambda here的解释。

如果您要将带有包装好的lambda的std::function传递给某个函数,而不是:

void foo(std::function<void()> f)
{
    std::thread runner(std::move(f));
    /* ... */
}

foo(std::function<void()>([](){}));

您可以这样做:

void foo(std::thread runner)
{
    /* ... */
}

foo(std::thread([](){}));

更新:这可以通过老式的方式完成。

std::thread runner([prom_deleter = proms.get_deleter(), prom = proms.release()]() mutable noexcept
{
    prom->set_value(80085);
    // if `proms` deleter is of a `default_deleter` type
    // the next line can be simplified to `delete prom;`
    prom_deleter(prom);
});