8

我刚刚阅读了文章 ' 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();

这种可组合性方法有什么问题?这是实现可组合性的有效方法吗?我们是否需要一些优雅的句法来超越这种模式?

4

2 回答 2

6

when_any并且when_all是构成期货的完全有效的方法。它们都对应于并行组合,其中组合操作等待一个或所有组合操作。

我们还需要顺序组合(Boost.Thread 中没有)。例如,这可能是一个future<T>::then函数,它允许您将使用未来值的操作排队并在未来准备好时运行。可以自己实现,但需要权衡效率。Herb Sutter 在他最近的 Channel9 视频中谈到了这一点。

N3428是将这些特性(以及更多特性)添加到 C++ 标准库的提案草案。它们都是库功能,不会向语言添加任何新语法。此外,N3328提议为将在内部使用的可恢复函数(如在 C# 中使用async/ )添加语法。awaitfuture<T>::then

于 2013-01-05T18:00:41.040 回答
3

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上都被阻止,在或分别执行之前,这两个都不会完成而且没有人会执行它们,因为我的程序被阻止了!fifi2ptpt2

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

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

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

于 2015-09-08T06:10:27.937 回答