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
运行。这比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已更新为N3857,N3865提供了更多便利功能。