C++常见的std::make_unique、std::packaged_task和std::promise问题

C++Common std::make_unique, std::packaged_task and std::promise Problem

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

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

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

解决方法 make_owner,与 make_unique 相似,只有一个关键区别,移动 复制只是转移对象销毁的控制权。它与 std::unique_ptr 基本相同,除了它是可复制的(它基本上总是移动,即使是在副本上)。恶心....

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

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

咕噜咕噜 它不像我想要的那样好,所以如果有人知道另一种避免使用共享指针或仅在调度程序中使用 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();

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

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

完全可以这样做:

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);
});