C++20 协程实战:从生成器到异步 IO 在上一篇文章中,我们了解了协程的基本概念。今天我们将不再纸上谈兵,而是深入到底层,编写三个具有实际意义的协程组件。
我们将涵盖:
泛型生成器 (Generator<T>) :像 Python 那样产生无限序列。
自定义等待体 (Awaiter) :理解 co_await 到底在等什么?
异步任务 (Task<T>) :如何实现协程之间的互相调用。
场景一:泛型生成器 (Generic Generator) 上一篇我们写死了一个 int 生成器。在实际开发中,我们通常需要一个通用的模板类,可以生成 int、string 甚至复杂的结构体。这也是最典型的“懒加载”应用场景。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <coroutine> #include <iostream> #include <optional> template <typename T>struct Generator { struct promise_type ; using handle_type = std::coroutine_handle<promise_type>; struct promise_type { std::optional<T> current_value; Generator get_return_object () { return Generator{handle_type::from_promise (*this )}; } std::suspend_always initial_suspend () { return {}; } std::suspend_always final_suspend () noexcept { return {}; } std::suspend_always yield_value (T value) { current_value = value; return {}; } void return_void () {} void unhandled_exception () { std::terminate (); } }; handle_type h; Generator (handle_type h) : h (h) {} ~Generator () { if (h) h.destroy (); } Generator (const Generator&) = delete ; Generator (Generator&& other) noexcept : h (other.h) { other.h = nullptr ; } struct Iterator { handle_type h; bool operator !=(std::default_sentinel_t ) const { return !h.done (); } void operator ++() { h.resume (); } T operator *() const { return *h.promise ().current_value; } }; Iterator begin () { if (h) h.resume (); return Iterator{h}; } std::default_sentinel_t end () { return {}; } };Generator<uint64_t > fibonacci (int max_count) { uint64_t a = 0 , b = 1 ; for (int i = 0 ; i < max_count; ++i) { co_yield a; auto temp = a; a = b; b = temp + b; } }int main () { std::cout << "Fibonacci sequence:" << std::endl; for (auto num : fibonacci (10 )) { std::cout << num << " " ; } std::cout << "\nDone." << std::endl; }
要点解析
模板化 :promise_type 中的 current_value 改为 T 类型。
迭代器支持 :我们添加了 begin() 和 end() 以及内部类 Iterator。这使得我们可以直接使用 C++ 的 for (auto x : gen) 语法,这才是现代 C++ 的味道。
场景二:理解 co_await 与自定义 Awaiter 这是 C++ 协程最难也最强大的部分。 当你写下 co_await X; 时,编译器其实是在问对象 X:“我需要挂起吗?如果挂起,我该把控制权交给谁? ”
我们通过实现一个非阻塞的“休眠”功能来演示。普通的 std::this_thread::sleep_for 会卡死线程,而我们的 AsyncSleep 只会挂起协程,线程可以去干别的事。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <coroutine> #include <iostream> #include <thread> #include <chrono> struct AsyncSleeper { int _ms; bool await_ready () const { return false ; } void await_suspend (std::coroutine_handle<> h) { std::thread ([h, this ]() { std::this_thread::sleep_for (std::chrono::milliseconds (_ms)); std::cout << " [Background] Timer done, resuming coroutine...\n" ; h.resume (); }).detach (); } void await_resume () {} };AsyncSleeper async_sleep (int ms) { return AsyncSleeper{ms}; }struct Task { struct promise_type { Task get_return_object () { return {}; } std::suspend_never initial_suspend () { return {}; } std::suspend_never final_suspend () noexcept { return {}; } void return_void () {} void unhandled_exception () {} }; };Task testAsync () { std::cout << "[Coroutine] Start sleeping..." << std::endl; co_await async_sleep (1000 ) ; std::cout << "[Coroutine] Resumed and finished!" << std::endl; }int main () { std::cout << "[Main] Call coroutine" << std::endl; testAsync (); std::cout << "[Main] Coroutine suspended, main thread continues work..." << std::endl; std::this_thread::sleep_for (std::chrono::milliseconds (1500 )); std::cout << "[Main] Exit" << std::endl; }
运行结果 1 2 3 4 5 6 [Main] Call coroutine [Coroutine] Start sleeping... [Main] Coroutine suspended, main thread continues work... [Background] Timer done, resuming coroutine... [Coroutine] Resumed and finished! [Main] Exit
要点解析 这个例子展示了协程真正的威力:异步非阻塞 。
await_suspend 是魔法发生的地方。我们在这里把控制权转移给了后台线程。
main 线程在协程挂起后,立即拿回了控制权,继续打印日志,没有被卡住。
场景三:协程组合 (Task waiting Task) 在真实业务中,我们很少只用一个协程,通常是协程调用协程(例如:HTTP 请求调用数据库查询,数据库查询调用 Socket 读取)。
为了实现 co_await OtherCoroutine();,协程的返回值类型(Task)本身必须也是一个 Awaiter 。
代码实现 这是一个简化的 Task<T> 实现,支持嵌套调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <coroutine> #include <iostream> #include <exception> template <typename T>struct Task { struct promise_type ; using handle_type = std::coroutine_handle<promise_type>; handle_type h; Task (handle_type h) : h (h) {} ~Task () { if (h) h.destroy (); } bool await_ready () { return false ; } void await_suspend (std::coroutine_handle<> caller) { h.promise ().previous = caller; h.resume (); } T await_resume () { return h.promise ().result; } struct promise_type { T result; std::coroutine_handle<> previous; Task get_return_object () { return Task{handle_type::from_promise (*this )}; } std::suspend_always initial_suspend () { return {}; } struct final_awaiter { bool await_ready () const noexcept { return false ; } void await_suspend (handle_type h) noexcept { if (auto prev = h.promise ().previous) { prev.resume (); } } void await_resume () noexcept {} }; final_awaiter final_suspend () noexcept { return {}; } void return_value (T value) { result = value; } void unhandled_exception () { std::terminate (); } }; };Task<int > add (int a, int b) { std::cout << " -> inner: calculating " << a << " + " << b << std::endl; co_return a + b; }Task<int > calculate_sum () { std::cout << "-> outer: calling add" << std::endl; int ret = co_await add (10 , 20 ); std::cout << "-> outer: got result " << ret << std::endl; co_return ret * 2 ; }int main () { auto t = calculate_sum (); t.h.resume (); std::cout << "Final result in main: " << t.h.promise ().result << std::endl; return 0 ; }
要点解析
对称传输 :协程 A 等待协程 B,A 必须把自己的句柄传给 B(存在 B.promise().previous 里)。
链式恢复 :当 B 结束时(final_suspend),它检查 previous,发现是 A,于是调用 A.resume()。A 醒来后通过 await_resume 拿到 B 的结果。
组合性 :这就是现代异步编程的核心——你可以像写同步代码一样,把复杂的异步逻辑层层封装。
总结 通过这三个例子,你应该能感受到 C++20 协程的各个层面:
Generator :利用 co_yield 做懒加载迭代器。
Awaiter :利用 co_await 和 await_suspend 对接异步系统(线程、IO)。
Task :利用 promise_type 存储父级句柄,实现协程的嵌套组合。
给读者的建议 : 如果你的博客读者主要是应用层开发者,建议重点介绍 Generator 和 AsyncSleep 的概念,因为 Task 的实现细节过于底层。你可以告诉他们:“在 C++23 或使用 folly/cppcoro 等库时,你们只需要写 co_await,而不需要写这些 promise_type。”