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 副本将转移控制权。主要区别在于副本也转移了控制权,就严格的语言规则而言,这可能有点禁忌,但我看不到另一种解决方法。
这个解决方案不好,因为:
- 它忽略复制语法。
- 它抛弃了 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();
还有一个 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);
});
问题 创建调度程序时,函数对象的最后一个副本或移动是函数对象最后一次被引用的地方(由工作线程)。如果您使用 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 副本将转移控制权。主要区别在于副本也转移了控制权,就严格的语言规则而言,这可能有点禁忌,但我看不到另一种解决方法。
这个解决方案不好,因为:
- 它忽略复制语法。
- 它抛弃了 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();
还有一个 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);
});