C++ 协程(Coroutine)使用教程:从基础概念到实战应用
一、什么是协程协程Coroutine是一种能够在执行过程中暂停、随后再恢复执行的函数。与普通函数不同协程可以在某个点挂起把控制权交还给调用者之后又能从挂起点继续执行。这种特性使得协程非常适合处理异步 I/O、生成器、惰性求值等场景。C20 将协程正式纳入标准库提供了一套底层的协程框架。需要注意的是C20 标准库只提供了协程的基础设施关键字和编译器支持包括三个新关键字co_await挂起当前协程等待某个操作完成co_yield挂起协程并返回一个值co_return结束协程并返回最终值要使用 C20 协程编译器需至少支持 C20 标准。主流编译器如 GCC 10、Clang 13、MSVC 2019 16.8 均已提供支持。二、C 协程的基础组件C20 协程模型由几个核心组件构成。理解这些组件是掌握协程编程的关键。2.1 promise_type每个协程都与一个 promise_type 对象关联该对象负责控制协程的行为。promise_type 需要实现以下核心方法get_return_object()创建协程的返回值对象initial_suspend()定义协程开始执行时的行为是立即执行还是先挂起final_suspend()定义协程结束时的行为通常用于对称转移unhandled_exception()处理协程中未捕获的异常return_void()或return_value()处理 co_return 的值2.2 协程返回类型与 Awaitable协程的返回类型必须是满足特定要求的类或结构体其中必须定义一个嵌套类型 promise_type。此外一个可等待对象Awaitable需要实现三个方法await_ready()检查操作是否已经完成返回 true 时不挂起await_suspend()在协程挂起时调用可接收协程句柄await_resume()协程恢复时调用返回最终结果标准库还提供了两个预定义的 Awaitablestd::suspend_always和std::suspend_never分别表示始终挂起和永不挂起。三、基本协程实现示例下面通过一个简单但完整的例子来展示 C 协程的各个组成部分如何协同工作。3.1 一个最简单的生成器首先实现一个基本协程返回类型它允许协程挂起和执行#include coroutine #include iostream struct Task { struct promise_type { Task get_return_object() { return Task{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; std::coroutine_handlepromise_type handle; Task(std::coroutine_handlepromise_type h) : handle(h) {} ~Task() { if (handle) handle.destroy(); } }; Task hello_coroutine() { std::cout Hello, Coroutine! std::endl; co_return; } int main() { auto task hello_coroutine(); std::cout Back in main std::endl; return 0; }3.2 理解 co_await 和挂起语义接下来扩展前面的例子展示 co_await 如何挂起和恢复协程#include coroutine #include iostream #include thread #include chrono struct Task { struct promise_type { Task get_return_object() { return Task{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; std::coroutine_handlepromise_type handle; Task(std::coroutine_handlepromise_type h) : handle(h) {} ~Task() { if (handle) handle.destroy(); } }; // 自定义 Awaitable模拟异步延时 struct AsyncDelay { int milliseconds; bool await_ready() const { return false; } void await_suspend(std::coroutine_handle h) { std::thread([h, ms milliseconds]() { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); h.resume(); }).detach(); } void await_resume() {} }; Task async_task() { std::cout 协程开始执行... std::endl; std::cout 等待 1 秒... std::endl; co_await AsyncDelay{1000}; std::cout 等待 2 秒... std::endl; co_await AsyncDelay{2000}; std::cout 协程执行完毕 std::endl; } int main() { auto task async_task(); std::cout 主线程继续执行其他任务... std::endl; // 等待足够时间以确保协程完成 std::this_thread::sleep_for(std::chrono::milliseconds(3500)); return 0; }四、co_yield 实现生成器co_yield 允许协程在挂起时返回一个值非常适合实现惰性生成器。下面的例子展示如何用协程实现一个整数序列生成器#include coroutine #include iostream #include optional templatetypename T struct Generator { struct promise_type { std::optionalT current_value; Generator get_return_object() { return Generator{std::coroutine_handlepromise_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 std::move(value); return {}; } void return_void() {} void unhandled_exception() {} }; std::coroutine_handlepromise_type handle; Generator(std::coroutine_handlepromise_type h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } Generator(const Generator) delete; Generator operator(const Generator) delete; Generator(Generator other) noexcept : handle(other.handle) { other.handle nullptr; } std::optionalT next() { if (!handle || handle.done()) { return std::nullopt; } handle.resume(); if (handle.done()) { return std::nullopt; } return handle.promise().current_value; } }; Generatorint fibonacci(int count) { int a 0, b 1; for (int i 0; i count; i) { co_yield a; int temp a b; a b; b temp; } } int main() { auto gen fibonacci(10); while (auto val gen.next()) { std::cout *val ; } std::cout std::endl; // 输出0 1 1 2 3 5 8 13 21 34 return 0; }五、协程与异常处理协程中的异常处理需要特别注意。promise_type 中的 unhandled_exception() 方法负责处理协程内部抛出的异常。以下示例展示了完整的异常处理机制#include coroutine #include iostream #include exception struct SimpleTask { struct promise_type { std::exception_ptr exception; SimpleTask get_return_object() { return SimpleTask{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { exception std::current_exception(); } }; std::coroutine_handlepromise_type handle; SimpleTask(std::coroutine_handlepromise_type h) : handle(h) {} ~SimpleTask() { if (handle) handle.destroy(); } void get_result() { if (handle.promise().exception) { std::rethrow_exception(handle.promise().exception); } } }; SimpleTask may_throw(bool should_throw) { if (should_throw) { throw std::runtime_error(协程内部抛出的异常); } std::cout 协程正常执行 std::endl; co_return; } int main() { auto task1 may_throw(false); try { task1.get_result(); std::cout task1 执行成功 std::endl; } catch (const std::exception e) { std::cout 捕获异常 e.what() std::endl; } auto task2 may_throw(true); try { task2.get_result(); } catch (const std::exception e) { std::cout 捕获异常 e.what() std::endl; } return 0; }六、实战异步文件读取器结合以上知识我们来构建一个更贴近实战的例子——使用协程实现异步文件读取。这个例子展示了协程在 I/O 密集型任务中的实际应用#include coroutine #include iostream #include fstream #include string #include thread #include future #include vector // 异步任务返回类型 struct AsyncTask { struct promise_type { std::string result; AsyncTask get_return_object() { return AsyncTask{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_value(std::string value) { result std::move(value); } void unhandled_exception() {} }; std::coroutine_handlepromise_type handle; AsyncTask(std::coroutine_handlepromise_type h) : handle(h) {} ~AsyncTask() { if (handle) handle.destroy(); } AsyncTask(const AsyncTask) delete; AsyncTask operator(const AsyncTask) delete; AsyncTask(AsyncTask other) noexcept : handle(other.handle) { other.handle nullptr; } std::string get_result() { if (!handle.done()) { handle.resume(); } if (!handle.done()) { return ; // 尚未完成 } return handle.promise().result; } }; // 异步文件读取 Awaitable struct AsyncReadFile { std::string filename; std::shared_ptrstd::string buffer; bool await_ready() const { return false; } void await_suspend(std::coroutine_handle h) { std::thread([this, h]() { std::ifstream file(filename); if (file.is_open()) { std::stringstream ss; ss file.rdbuf(); *buffer ss.str(); } h.resume(); }).detach(); } std::string await_resume() { return *buffer; } }; AsyncTask read_files() { std::cout 开始异步读取文件... std::endl; auto buf1 std::make_sharedstd::string(); std::string content1 co_await AsyncReadFile{file1.txt, buf1}; std::cout 文件 1 读取完毕大小 content1.size() 字节 std::endl; auto buf2 std::make_sharedstd::string(); std::string content2 co_await AsyncReadFile{file2.txt, buf2}; std::cout 文件 2 读取完毕大小 content2.size() 字节 std::endl; std::string result 合并内容\n content1 \n---\n content2; co_return result; }七、协程调度与对称转移在高性能协程框架中对称转移Symmetric Transfer是一个重要的优化技术。它允许协程在挂起时直接跳转到另一个协程而不需要先返回调度器从而减少了多余的上下文切换开销。对称转移的核心在于 final_suspend 返回一个 std::coroutine_handle告诉编译器应该继续执行哪个协程#include coroutine #include iostream struct SymmetricTask { struct promise_type { std::coroutine_handle continuation; SymmetricTask get_return_object() { return SymmetricTask{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } auto final_suspend() noexcept { // 对称转移协程结束时跳转到 continuation struct FinalAwaiter { std::coroutine_handle continuation; bool await_ready() noexcept { return false; } std::coroutine_handle await_suspend(std::coroutine_handle) noexcept { return continuation; } void await_resume() noexcept {} }; return FinalAwaiter{continuation}; } void return_void() {} void unhandled_exception() {} }; std::coroutine_handlepromise_type handle; SymmetricTask(std::coroutine_handlepromise_type h) : handle(h) {} ~SymmetricTask() { if (handle) handle.destroy(); } };通过这种方式可以实现高效的任务调度避免传统回调地狱同时在性能关键场景中减少不必要的开销。八、常用协程库推荐C20 只提供了协程的底层原语实际开发中建议使用成熟的第三方库来简化协程编程cppcoro一个流行的 C 协程库提供了生成器、任务、异步 I/O 等多种实用工具帮助开发者快速上手协程编程Boost.Coroutine2Boost 库中的协程组件支持 C14 及以上标准提供堆栈式协程实现libunifexFacebook 出品的异步编程框架基于 Sender/Receiver 模型深度集成 C20 协程folly::coroMetaFacebook开源的高性能协程库适用于大规模分布式系统中的异步任务管理C20 协程为异步编程提供了一种优雅且强大的表达方式。以下是几点最佳实践建议生命周期管理始终注意协程句柄的销毁避免内存泄漏。建议使用 RAII 模式封装 coroutine_handle异常处理在 promise_type 中妥善处理 unhandled_exception避免程序因未捕获异常而终止性能考量在高频调用的场景中优先使用对称转移技术减少不必要的调度开销使用成熟库避免从零开始实现协程基础设施善用 cppcoro 等社区验证过的方案避免过度使用并非所有场景都适合使用协程简单的同步调用有时更清晰易维护掌握 C 协程需要一定的学习曲线但一旦理解其核心机制就能编写出简洁、高效且易于维护的异步代码。