未来的可组合性,以及提升::wait_for_all
future composability, and boost::wait_for_all
我刚刚读了文章"Futures Done Right",c ++ 11 承诺缺乏的主要内容似乎是从现有期货创建复合期货
我现在正在查看 boost::wait_for_any 的文档
但请考虑以下示例:
int calculate_the_answer_to_life_the_universe_and_everything()
{
return 42;
}
int calculate_the_answer_to_death_and_anything_in_between()
{
return 121;
}
boost::packaged_task<int> pt(calculate_the_answer_to_life_the_universe_and_everything);
boost:: future<int> fi=pt.get_future();
boost::packaged_task<int> pt2(calculate_the_answer_to_death_and_anything_in_between);
boost:: future<int> fi2=pt2.get_future();
....
int calculate_the_oscillation_of_barzoom(boost::future<int>& a, boost::future<int>& b)
{
boost::wait_for_all(a,b);
return a.get() + b.get();
}
boost::packaged_task<int> pt_composite(boost::bind(calculate_the_oscillation_of_barzoom, fi , fi2));
boost:: future<int> fi_composite=pt_composite.get_future();
这种可组合性方法有什么问题?这是实现可组合性的有效方法吗? 我们是否需要一些优雅的语法教育来克服这种模式?
when_any
和when_all
是构成期货的完全有效的方法。它们都对应于并行组合,其中复合操作等待一个或所有组合操作。
我们还需要顺序组合(这在 Boost.Thread 中没有(。例如,这可以是一个future<T>::then
函数,允许您对使用未来值并在未来准备就绪时运行的操作进行排队。可以自己实现这一点,但需要权衡效率。赫伯·萨特(Herb Sutter(在他最近的Channel9视频中谈到了这一点。
N3428 是将这些功能(以及更多(添加到C++标准库中的提案草案。它们都是库功能,不会向语言添加任何新语法。此外,N3328 是为可恢复函数添加语法的建议(例如在 C# 中使用 async
/await
(,这些函数将在内部使用 future<T>::then
。
使用"edulcorant"一词的要点。 :)
示例代码的问题在于,您将所有内容打包到任务中,但从不安排这些任务执行!
int calculate_the_answer_to_life() { ... }
int calculate_the_answer_to_death() { ... }
std::packaged_task<int()> pt(calculate_the_answer_to_life);
std::future<int> fi = pt.get_future();
std::packaged_task<int()> pt2(calculate_the_answer_to_death);
std::future<int> fi2 = pt2.get_future();
int calculate_barzoom(std::future<int>& a, std::future<int>& b)
{
boost::wait_for_all(a, b);
return a.get() + b.get();
}
std::packaged_task<int()> pt_composite([]{ return calculate_barzoom(fi, fi2); });
std::future<int> fi_composite = pt_composite.get_future();
如果此时我写
pt_composite();
int result = fi_composite.get();
我的程序将永远阻止。它永远不会完成,因为pt_composite
在calculate_barzoom
上被阻止,在wait_for_all
上被阻止,在fi
和fi2
上都被阻止,直到有人分别执行pt
或pt2
,否则两者都不会完成。没有人会执行它们,因为我的程序被阻止了!
你可能想让我写这样的东西:
std::async(pt);
std::async(pt2);
std::async(pt_composite);
int result = fi_composite.get();
这将起作用。但它的效率极低——我们生成三个工作线程(通过三次调用async
(,以执行两个线程的工作。第三个线程 - 运行pt_composite
的线程 - 将立即生成,然后坐在那里睡觉,直到pt
和pt2
完成运行。这比旋转要好,但比不存在要糟糕得多:这意味着我们的线程池比它应该少一个工人。在一个合理的线程池实现中,每个 CPU 内核只有一个线程,并且一直在进行大量任务,这意味着我们有一个 CPU 内核处于空闲状态,因为本应在该内核上运行的工作线程当前在 wait_for_all
内部被阻止。
我们想要做的是以声明方式声明我们的意图:
int calculate_the_answer_to_life() { ... }
int calculate_the_answer_to_death() { ... }
std::future<int> fi = std::async(calculate_the_answer_to_life);
std::future<int> fi2 = std::async(calculate_the_answer_to_death);
std::future<int> fi_composite = std::when_all(fi, fi2).then([](auto a, auto b) {
assert(a.is_ready() && b.is_ready());
return a.get() + b.get();
});
int result = fi_composite.get();
然后让库和调度程序协同工作以执行正确的操作:不要生成任何无法立即继续其任务的工作线程。如果最终用户必须编写一行显式休眠、等待或阻塞的代码,那么肯定会失去某些性能。
换句话说:在它的时间之前不生成任何工作线程。
显然,可以在标准C++中完成所有这些操作,而无需库支持;这就是库本身的实现方式!但是从头开始实施是一个巨大的痛苦,有许多微妙的陷阱;这就是为什么图书馆支持似乎很快就会到来是一件好事。
Roshan Shariff的回答中提到的ISO提案N3428已更新为N3857,N3865提供了更多便利的功能。
- std::condition_variable::wait()如何评估给定的谓词
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- std::memory_order for std::atomic:<T>:wait
- 方法错误"not all control paths return a value"和方法不返回值
- std::p romise::set_value() 和 std::future::wait() 是否提供内存围栏?
- 生成文件:6:目标'all'的配方在 Ubuntu-16.04 上失败
- 对于等待以 std::future wait() 返回的函数的 CPU 使用率或检查标志在循环中休眠一段时间哪个更好?
- 每次都会重建多个程序的制作文件,尽管配方"all"被标记为虚假
- 制造:对'all'错误无能为力
- 为什么Eigen不能解析内置符号?(all、last、seq等)
- std::future::get()或std::future::wait()是std::thread::join()的替
- 警告"C++ requires a type specifier for all declaration"地图
- gcc -O2 比 gcc -O2 -g 小,后跟 strip --strip-all
- 在 while 循环中使用 std::condition_variable::wait 是否正确
- 警告:"Not all control paths return a value" c++
- future::wait() 是否与 async() 执行线程的完成同步?
- 为什么'wait with predicate'求解条件变量的'lost wakeup'?
- 线程锁定互斥锁的速度比 std::conditional_variable::wait() 快
- AVX2浮子比较并获得0.0或1.0,而不是all-0或全零件
- 链接时,"grab what you need" 和 "grab all" 之间是否有某些内容(-wl,--whole-archive)?