【问题标题】:C++Common std::make_unique, std::packaged_task and std::promise ProblemC++常见的std::make_unique、std::packaged_task和std::promise问题
【发布时间】:2018-09-01 05:06:13
【问题描述】:

问题 创建调度程序时,函数对象的最后一个副本或移动是函数对象被引用的最后一个位置(由工作线程)。如果您要使用 std::function 在调度程序中存储函数,那么任何 std::promises 或 std::packaged_task 或其他类似的仅移动类型都不起作用,因为它们不能被 std::function 复制。

同样,如果您要在调度程序中使用 std::packaged_task 它会带来不必要的开销,因为许多任务根本不需要打包任务返回的 std::future。

常见但不是很好的解决方案是使用 std::shared_ptrstd::promise> 或 std::shared_ptrstd::packaged_task>工作,但它会带来相当多的开销。

解决方案 make_owner 与 make_unique 类似,但有一个关键区别,移动 OR 复制只是转移了对对象销毁的控制。它基本上与 std::unique_ptr 相同,只是它是可复制的(它基本上总是移动,即使在副本上也是如此)。毛……

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

这个解决方案不好,因为:

  • 它忽略复制语义。
  • 它抛弃了 const(在复制构造函数和运算符 = 中)

咕噜 它不像我想要的那样好的解决方案,所以如果有人知道另一种方法来避免使用共享指针或只在调度程序中使用 packaged_tasks,我很乐意听到它,因为我很难过......

我对这个解决方案很不满意......有什么想法吗? 我能够使用移动语义重新实现 std::function 但这似乎是一个巨大的痛苦,并且它在对象生命周期方面有其自身的问题(但在使用带有引用捕获的 std::function 时它们已经存在)。

问题的一些例子:

编辑 请注意,在目标应用程序中,我不能执行 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();

还有一个打包任务的例子

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 不适合我的应用程序。

【问题讨论】:

标签: c++ c++17 scheduler


【解决方案1】:

主要问题是:为什么要在传递给 std::thread 构造函数之前用 std::function 包装一个 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);
});

【讨论】:

  • 重点是线程池,线程不断运行消耗可用功能。在这种情况下,我无法转到该线程,抱歉,我对此问题不太清楚。
  • 任务被放入无锁双端队列中,并且是工作窃取调度程序的一部分。您如何看待我在复制 unique_ptr 版本时编写移动的建议?
  • 澄清一下,您有一个std::functions 的容器(队列),并且您想在其中存储仅移动的 lambda?我建议你下次更准确,因为你的问题包含很多不相关的东西。
  • “复制unique_ptr版本时的移动”这听起来像auto_ptr。我有个更好的主意,很快就会更新答案。
  • 太棒了!今晚测试。
猜你喜欢
  • 1970-01-01
  • 2014-11-10
  • 1970-01-01
  • 2016-06-07
  • 2014-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
相关资源
最近更新 更多