CppCoreGuidelines CP.61 通过 async() 生成同步任务
14 July 2023
“Use async()
to spawn concurrent tasks”
理由
与 R.12 类似。R.12 提出避免使用原始指针。这里我们提出要尽量避免原始线程和原始原语。应该用工厂函数,比如 std::async
来生成线程和重用线程,从而避免在你的代码中暴露原始线程。
例子
int read_value(const std::string& filename) { std::ifstream in(filename); in.exceptions(std::ifstream::failbit); int value; in >> value; return value; } void async_example() { try { std::future<int> f1 = std::async(read_value, "v1.txt"); std::future<int> f2 = std::async(read_value, "v2.txt"); std::cout << f1.get() + f2.get() << '\n'; } catch (const std::ios_base::failure& fail) { // handle exception here } }
注意
很遗憾, std::async
也不是很完美。比如说,它不能利用线程池。这意味着它不会把任务丢进队列等待执行,可能会因为资源耗尽而运行失败。但是,就算你不能利用 std::async
你也应该选择编写自己的函数,可以等待未来返回值。而尽量避免使用原始原语。
坏例子
这个例子显示了两种利用 std::future
的方式。但是无法避免的,需要管理原始 std::thread
。
void async_example() { std::promise<int> p1; std::future<int> f1 = p1.get_future(); std::thread t1([p1 = std::move(p1)]() mutable { p1.set_value(read_value("v1.txt")); }); t1.detach(); // evil std::packaged_task<int()> pt2(read_value, "v2.txt"); std::future<int> f2 = pt2.get_future(); std::thread(std::move(pt2)).detach(); std::cout << f1.get() + f2.get() << '\n'; }
好例子
这个例子展示了使用 std::async
的通用模式。如果程序环境不允许使用
std::async
本身的话,可以这么写:
void async_example(WorkQueue& wq) { std::future<int> f1 = wq.enqueue([]() { return read_value("v1.txt"); }); std::future<int> f2 = wq.enqueue([]() { return read_value("v2.txt"); }); std::cout << f1.get() + f2.get() << '\n'; }
任何需要处理 read_value
的线程都隐藏在 WorkQueue::enqueue
之后。用户代码只要处理 future
对象,而不用处理原始线程、原始原语以及 packaged_task
对象。