未来的可组合性,以及提升::wait_for_all

future composability, and boost::wait_for_all

本文关键字:all wait for 可组合 未来      更新时间:2023-10-16

我刚刚读了文章"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_anywhen_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_compositecalculate_barzoom上被阻止,在wait_for_all上被阻止,在fifi2上都被阻止,直到有人分别执行ptpt2,否则两者都不会完成。没有人会执行它们,因为我的程序被阻止了!

你可能想让我写这样的东西:

std::async(pt);
std::async(pt2);
std::async(pt_composite);
int result = fi_composite.get();

这将起作用。但它的效率极低——我们生成三个工作线程(通过三次调用async(,以执行两个线程的工作。第三个线程 - 运行pt_composite的线程 - 将立即生成,然后坐在那里睡觉,直到ptpt2完成运行。这比旋转要好,但比不存在要糟糕得多:这意味着我们的线程池比它应该少一个工人。在一个合理的线程池实现中,每个 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提供了更多便利的功能。