本文档用于比较协程与传统同步写法与 Future/Promise 写法的异同。本文档使用的代码可在 async_simple/demo_example/ReadFiles 中找到。
ReadFiles 是一个计算多个文件中特定字符出现次数的程序。使用了三种写法实现这一功能:
- 传统同步写法。
- 基于 Future/Promise 的异步写法。
- 使用协程(Lazy)的写法。
同步写法
cpp
uint64_t CountCharInFiles(const std::vector<FileName> &Files, char c) {
uint64_t ReadSize = 0;
for (const auto &File : Files)
ReadSize += CountCharInFile(File, c);
return ReadSize;
}
同步的写法非常直观,只需要依次遍历一遍即可了。
异步写法
虽然同步的写法非常简单直观,但当 File 数量很多,File 平均内容也很多,甚至 File 存储于远端时,程序员会考虑使用异步写法来提升效率。
cpp
Future<uint64_t> CountCharInFilesAsync(const std::vector<FileName> &Files, char c) {
// std::shared_ptr is necessary here. Since ReadSize may be used after
// CountCharInFilesAsync function ends.
auto ReadSize = std::make_shared<uint64_t>(0);
// We need to introduce another API `do_for_each` here.
return do_for_each(std::move(Files), [ReadSize, c](auto &&File){
return CountCharInFileAsyncImpl(File, c).thenValue([ReadSize](auto &&Size) {
*ReadSize += Size;
return makeReadyFuture(Unit());
});
}).thenTry([ReadSize] (auto&&) { return makeReadyFuture<uint64_t>(*ReadSize); });;
}
上面的例子是该功能对应的异步版本。可以看到,我们不但需要引入 std::shared_ptr
与新的 API do_for_each
,同时代码结构也复杂了非常多。
协程写法
通过协程,程序员可以用同步写法写异步程序。兼具开发效率与运行效率。
cpp
Lazy<uint64_t> CountCharInFilesCoro(const std::vector<FileName> &Files, char c) {
uint64_t ReadSize = 0;
for (const auto &File : Files)
ReadSize += co_await CountCharFileCoroImpl(File, c);
co_return ReadSize;
}
我们可以看到,使用协程的写法与基于同步的写法基本一致,简单直观。而在运行效率上,协程版本也会好于异步版本。一方面协程版本不需要 std::shared_ptr
,另一方面相比于异步版本,协程版本的代码更少且更连续,会更方便编译器做优化。