# C++20协程 **摘要**:C++20 引入的协程机制为异步编程提供了轻量级解决方案,其核心优势在于通过用户态调度实现高效的上下文切换,适用于 I/O 密集型任务、生成器模式等场景。本文系统阐述 C++20 协程的底层原理与实践要点,首先解析协程的基本结构,包括promise_type状态控制器、coroutine_handle句柄及co_await、co_yield、co_return关键字的工作机制,揭示协程启动、暂停、恢复与终止的完整生命周期。其次,深入探讨协程的实现细节,如 Awaitable 与 Awaiter 对象的转换逻辑、协程帧的内存管理(分配与释放)、编译器对协程的状态机转换(基于暂停点索引的执行控制)。针对协程使用中的关键问题,分析内存分配优化策略(自定义分配器与内存池)、协程与线程的本质区别(用户态调度 vs 内核态调度),以及对称转移导致的栈溢出风险及解决方案(尾调用优化与std::noop_coroutine)。本文通过实例与伪代码还原协程的编译器转换过程,为开发者理解协程机制、规避常见问题提供理论与实践参考。 **关键词**:C++20;协程;异步编程;promise_type;上下文切换 ## 1 协程简介   C++20 引入了协程(Coroutines)支持。协程一种能够暂停和恢复执行的特殊函数,非常适合处理异步操作、生成器、状态机等场景,相比于通过回调函数、线程等方式,协程能够更清晰地组织代码,减少上下文切换的开销,提高代码的可读性和可维护性。 ### 1.1 协程和函数   在 C++ 中协程是一种特殊的函数调用,它与普通函数存在显著区别。普通函数一旦开始执行,会一直运行到返回语句或函数结束,期间无法暂停;而协程在执行过程中可以在特定点暂停,保存当前的执行状态(包括局部变量、指令指针等),之后在合适的时机可以从暂停点恢复执行,继续完成后续操作。   从执行流程来看,普通函数的调用是一种栈式的调用方式,遵循 “先进后出” 的原则,每次调用都会在调用栈上分配新的栈帧;协程则拥有自己独立的状态存储,其暂停和恢复不依赖于传统的调用栈,这使得协程的上下文切换成本远低于线程切换。   简单回顾下普通函数的调用流程,一个普通的函数调用流程如下: 1. 函数调用(call):程序将控制权转移给被调用函数,同时将返回地址压入调用栈。 2. 函数执行(execute):被调用函数开始执行,它会使用栈帧来存储局部变量、参数、返回值等。 3. 函数返回(return):被调用函数执行完毕,将返回值压入栈帧,然后将控制权返回给调用函数。 4. 调用栈弹出(pop):调用函数从调用栈中弹出返回地址,恢复控制权。   其中,函数调用时会保存当前的执行状态(包括局部变量、指令指针等),并将控制权交给被调用函数。当被调用函数执行完毕后,会从调用栈中弹出返回地址,恢复调用函数的执行状态,继续执行后续操作。也就是说函数一旦被调用就没有回头路,直到函数调用结束才会将控制权还给调用函数。   而相比之下协程的调用路程要复杂的多: 1. 协程启动(start):当协程被调用时,系统会为其分配独立的状态存储(不同于传统栈帧),用于保存局部变量、指令指针、寄存器状态等信息。此时协程进入初始状态,可根据其返回类型的```initial_suspend```策略决定是否立即执行或暂停。 2. 协程执行(execute):协程开始执行,与普通函数类似,但在遇到```co_await```、```co_yield```等关键字时会触发暂停逻辑。 - 若执行```co_await expr```,协程会先计算表达式```expr```得到一个 “等待体”(awaitable),然后调用该等待体的```awit_suspend```方法。若该方法返回true或一个协程句柄,当前协程会暂停,将控制权交还给调用者或切换到其他协程;若返回false,则继续执行。 - 若执行```co_yield expr```,本质是通过特殊的等待体暂停协程,并向调用者返回一个值,之后可从暂停点恢复。 3. 协程暂停(suspend):协程暂停时,其当前执行状态被完整保存到独立存储中,调用栈不会被销毁。此时控制权返回给调用者或调度器,调用者可继续执行其他任务,或在合适时机恢复协程。 4. 协程恢复(resume):调用者通过协程句柄的```resume()```方法触发协程恢复。系统从保存的状态中还原执行环境,协程从暂停点继续执行后续代码。 5. 协程结束(finalize):当协程执行到```co_return```语句或函数末尾时,会触发```final_suspend```策略。通常此时协程会进入最终暂停状态,等待调用者通过句柄销毁其状态存储,以释放资源。   这种流程使得协程能够在执行过程中多次暂停和恢复,且每次切换仅涉及状态存储的读写,无需像线程那样切换内核态上下文,因此效率更高。同时,协程的状态保持特性让异步操作的代码编写更接近同步逻辑,大幅降低了回调嵌套带来的复杂性。 ### 1.2 简单的协程   下面是一个简单的 C++20 协程示例,展示了协程的基本使用方式: ```cpp #include #include #include #include #include struct SimpleCorontinePromise; struct SimpleCorontine { using promise_type = SimpleCorontinePromise; }; struct SimpleCorontinePromise { SimpleCorontine get_return_object() { SPDLOG_INFO("get_return_object"); return {}; } void return_void() { SPDLOG_INFO("return_void"); } std::suspend_never initial_suspend() noexcept { SPDLOG_INFO("initial_suspend"); return {}; } std::suspend_never final_suspend() noexcept { SPDLOG_INFO("final_suspend"); return {}; } void unhandled_exception() { SPDLOG_INFO("unhandled_exception"); } }; SimpleCorontine MySimpleCorontine() { SPDLOG_INFO("Corontine Start"); co_return; SPDLOG_INFO("Corontine End"); } int testSimpleCorontine() { SPDLOG_INFO("Main thread started executing 1"); auto coro = MySimpleCorontine(); // Ensure the lifecycle of coro is managed SPDLOG_INFO("Main thread started executing 2"); return 0; } ```   上面的代码定义了一个简单的协程```MySimpleCorontine```,其包括: 1. 协程函数```MySimpleCorontine```,用于定义协程的执行逻辑。 2. 协程Promise类型```SimpleCorontinePromise```,用于定义协程的状态管理和返回值处理。```Promise```必须 - 定义```get_return_object()```方法,用于返回协程对象。 - 定义```return_void()```方法,用于处理协程函数执行完毕后的逻辑。 - 定义```initial_suspend()```方法,用于定义协程的初始暂停策略。 - 定义```final_suspend()```方法,用于定义协程的最终暂停策略。 - 定义```unhandled_exception()```方法,用于处理协程执行过程中发生的异常。 3. 协程类型```SimpleCorontine```,用于表示协程对象。而```SimpleCorontine```必须定义```promise_type```成员,用于指定Promise类型。 > 在协程的第一行调用```get_return_object```是为了确保返回对象的有效性、管理状态和资源、提供异常安全性、简化控制流以及满足编译器设计的需要。   其执行结果如下: ```bash [2025-07-23 21:04:45.247] [info] [SimpleCorontine.cpp:44] Main thread started executing 1 [2025-07-23 21:04:45.247] [info] [SimpleCorontine.cpp:14] get_return_object [2025-07-23 21:04:45.247] [info] [SimpleCorontine.cpp:23] initial_suspend [2025-07-23 21:04:45.247] [info] [SimpleCorontine.cpp:38] Corontine Start [2025-07-23 21:04:45.248] [info] [SimpleCorontine.cpp:19] return_void [2025-07-23 21:04:45.248] [info] [SimpleCorontine.cpp:28] final_suspend [2025-07-23 21:04:45.248] [info] [SimpleCorontine.cpp:47] Main thread started executing 2 ```   从执行结果,我们可以整理出其基本的调用流程,能够注意到```co_return```之后的代码不会被执行,这是因为```co_return```会触发```final_suspend```策略,导致协程进入最终暂停状态。 ![](https://cdn.jsdelivr.net/gh/grayondream/MyImageBlob@main/imgs/corontine_simple_call.drawio.svg) ### 1.3 协程```resume```   上面的代码中,我们并没有控制协程的调用流程,似乎协程只是按照某种约定按照顺序调用规定的函数(虽然事实也是如此)。我们尝试将代码修改成下面的样子,通过```resume```来控制协程的调用流程。改动如下,完整的代码见 ```cpp #include #include #include #include #include struct SimpleCoroutinePromise; struct SimpleCoroutine { using promise_type = SimpleCoroutinePromise; std::coroutine_handle handle; SimpleCoroutine(std::coroutine_handle handle) : handle(handle) {} SimpleCoroutine(const SimpleCoroutine&) = delete; SimpleCoroutine& operator=(const SimpleCoroutine&) = delete; SimpleCoroutine(SimpleCoroutine&& other) noexcept : handle(other.handle) { other.handle = nullptr; } SimpleCoroutine& operator=(SimpleCoroutine&& other) noexcept { if (this != &other) { if (handle) { handle.destroy(); // Destroy the current handle if it exists } handle = other.handle; other.handle = nullptr; } return *this; } void resume() { if (handle) { handle.resume(); } } ~SimpleCoroutine() { if (handle) { handle.destroy(); } } }; struct SimpleCoroutinePromise { SimpleCoroutine get_return_object() { SPDLOG_INFO("get_return_object"); return SimpleCoroutine(std::coroutine_handle::from_promise(*this)); } void return_void() { SPDLOG_INFO("return_void"); } std::suspend_always initial_suspend() noexcept { SPDLOG_INFO("initial_suspend"); return {}; } std::suspend_always final_suspend() noexcept { SPDLOG_INFO("final_suspend"); return {}; } void unhandled_exception() { SPDLOG_INFO("unhandled_exception"); } }; SimpleCoroutine MySimpleCoroutine() { SPDLOG_INFO("Coroutine Start"); co_return; // This will directly return, and the coroutine ends here SPDLOG_INFO("Coroutine End"); // This line will not be executed } int testSimpleCorontine() { SPDLOG_INFO("Main thread started executing 1"); auto coro = MySimpleCoroutine(); SPDLOG_INFO("Main thread started executing 2"); coro.resume(); SPDLOG_INFO("Main thread started executing 3"); return 0; } ```   上面的代码输出结果为: ```cpp [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:78] Main thread started executing 1 [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:48] get_return_object [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:57] initial_suspend [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:80] Main thread started executing 2 [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:72] Coroutine Start [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:53] return_void [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:62] final_suspend [2025-07-23 22:31:34.783] [info] [SimpleCorontine.cpp:82] Main thread started executing 3 ```   上面的输出相比之前的输出能够发现,执行完协程的初始化相关函数之后,协程就讲控制权交给了主函数,主函数通过```resume```来恢复协程的执行,之后再执行协程相关的代码。相关的改动: 1. 创建一个 handle 来管理协程的生命周期。这个 handle 允许我们控制何时恢复协程的执行以及最终何时销毁它。 2. 修改suspend状态,其中: 1. ```suspend_always```:协程在达到指定的挂起点(如```initial_suspend()```)时会暂时停止执行,并将控制权返回给调用者。调用者随后可以选择何时恢复协程的执行。 2. ```suspend_never```:协程在到达挂起点时直接继续执行,控制权不会返回给调用者。这意味着协程会在达到终点后直接终止,而不会暂停。 ### 1.4 协程yield   除了基本的结构,协程还有其他的功能,比如```yield```。```co_yield```是 C++20 协程中用于 “产出值并暂停” 的关键字,主要用于实现生成器(Generator)模式,允许协程在执行过程中多次返回值,每次返回后暂停,等待下次被恢复时继续执行。```co_yield```本质上是一种语法糖,等价于```co_await promise.yield_value(expr)```。其工作流程如下: - 当协程执行到```co_yield expr ```时,首先计算表达式```expr```的值; - 将该值存储到```promise_type```中,供调用者获取; - 协程暂停执行,将控制权交还给调用者; - 当调用者通过协程句柄恢复协程时,协程从 co_yield 之后的代码继续执行。   基于此,我们可以实现一个简单的generator用来生成数字。 ```cpp struct Generator { struct GeneratorPromise { using Handle = std::coroutine_handle; Generator get_return_object() { return Generator(Handle::from_promise(*this)); } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} std::suspend_always yield_value(int v) { value = v; return {}; } int value{}; }; using promise_type = GeneratorPromise; using Handle = std::coroutine_handle; Generator(Handle handle) : handle(handle) {} ~Generator() { if (handle) { handle.destroy(); } } Generator(const Generator&) = delete; Generator& operator=(const Generator&) = delete; Generator(Generator&& other) noexcept : handle(other.handle) { other.handle = nullptr; } Generator& operator=(Generator&& other) noexcept { if (this != &other) { if (handle) { handle.destroy(); } handle = other.handle; other.handle = nullptr; } return *this; } bool done(){ return handle.done(); } int next(){ if(done()){ return - 1; } handle.resume(); if (done()) { return - 1; } return handle.promise().value; } Handle handle; }; Generator GeneratorNum(){ for(int i = 0;i < 5;i ++){ co_yield i; } } int testSimpleGenerator() { auto gen = GeneratorNum(); while(!gen.done()){ SPDLOG_INFO("num {}", gen.next()); } return 0; } ```   代码的输出为: ```cpp [2025-07-23 22:50:28.006] [info] [SimpleCorontine.cpp:165] num 0 [2025-07-23 22:50:28.007] [info] [SimpleCorontine.cpp:165] num 1 [2025-07-23 22:50:28.007] [info] [SimpleCorontine.cpp:165] num 2 [2025-07-23 22:50:28.007] [info] [SimpleCorontine.cpp:165] num 3 [2025-07-23 22:50:28.007] [info] [SimpleCorontine.cpp:165] num 4 [2025-07-23 22:50:28.007] [info] [SimpleCorontine.cpp:165] num -1 ``` ### 1.5 协程```co_return```   co_return 是用于终止协程执行并返回结果的关键字,类似于普通函数中的 return,但专门针对协程的特性设计,用于结束协程的生命周期并传递最终结果。co_return 的语法有两种形式: - 无返回值:co_return。用于不需要返回最终结果的协程,仅表示协程执行结束。 - 带返回值:co_return 表达式。用于需要向调用者返回最终结果的协程,表达式的值会被传递给协程的```promise_type```(promise_type)。   当协程执行到```co_return```时,会触发以下流程: - 处理返回值: - 若为```co_return expr```;,则表达式 ```expr``` 的值会被传递给协程```promise_type```的 ```return_value(expr)``` 方法(需在 ```promise_type``` 中定义),由```promise_type```存储该结果,供调用者获取。 - 若为 ```co_return```;,则调用```promise_type```的 ```return_void()``` 方法(无返回值场景)。 - 触发最终挂起: - 协程执行完返回值处理后,会调用```promise_type```的 ```final_suspend()``` 方法,根据其返回的挂起策略(```std::suspend_always``` 或 ```std::suspend_never```)决定是否暂停。 - 通常会返回 ```std::suspend_always```,让协程进入最终暂停状态,等待调用者通过协程句柄销毁资源。 - 协程终止:协程进入最终暂停状态后,其生命周期并未完全结束,需等待调用者显式调用```coroutine_handle::destroy()```释放协程占用的资源(如状态存储、局部变量等)。   之前的协程例子是不带返回值的,这里通过```reutrn_value```来处理返回值。 ```cpp // 带返回值的协程返回类型 struct ResultTask { struct promise_type { std::string result; // 存储协程的返回结果 // 获取协程返回对象 ResultTask get_return_object() { return ResultTask{ std::coroutine_handle::from_promise(*this) }; } // 初始挂起:立即执行 std::suspend_never initial_suspend() { return {}; } // 最终挂起:暂停以等待销毁 std::suspend_always final_suspend() noexcept { return {}; } // 处理带值的 co_return void return_value(const std::string& val) { result = val; // 保存返回值 } // 异常处理 void unhandled_exception() { std::terminate(); } }; std::coroutine_handle handle; // 提供接口让调用者获取返回值 std::string get_result() const { return handle.promise().result; } }; // 示例协程:执行一些操作后返回结果 ResultTask process_data(int input) { SPDLOG_INFO("process_data"); SPDLOG_INFO("input {}", input); // 模拟一些处理逻辑 if (input < 0) { co_return "error, the input is negative"; // 返回错误信息 } int result = input * 2; co_return "process data done: " + std::to_string(result); // 返回计算结果 } int testResultTask() { // 启动协程 auto task = process_data(10); SPDLOG_INFO("task handle done {}", task.handle.done()); // 检查协程是否完成 if (task.handle.done()) { SPDLOG_INFO("task handle done {}", task.handle.done()); SPDLOG_INFO("task result {}", task.get_result()); } // 释放协程资源 task.handle.destroy(); return 0; } ```   上述代码的输出结果是: ```bash [2025-07-24 08:47:39.055] [info] [SimpleCorontine.cpp:209] process_data [2025-07-24 08:47:39.055] [info] [SimpleCorontine.cpp:210] input 10 [2025-07-24 08:47:39.055] [info] [SimpleCorontine.cpp:224] task handle done true [2025-07-24 08:47:39.055] [info] [SimpleCorontine.cpp:228] task handle done true [2025-07-24 08:47:39.055] [info] [SimpleCorontine.cpp:229] task result process data done: 20 ``` ### 1.6 协程```await```   ```co_await```是核心关键字之一,用于 “等待一个异步操作完成”,并在等待期间暂停当前协程,将控制权交还给调用者或调度器。当被等待的操作完成后,协程可以从暂停点恢复执行。```co_await```后跟一个 “可等待对象”(awaitable),语法形式为: ```cpp co_await 可等待对象; ```   “可等待对象” 是指实现了特定接口(3 个核心方法)的对象,它代表一个可能尚未完成的异步操作(如网络请求、文件 I/O 等)。一个对象要能被 co_await 等待,必须满足以下条件(或通过适配器转换后满足): - ```await_ready()```:判断操作是否已完成。 - 返回 true:操作已完成,co_await 不暂停,直接继续执行。 - 返回 false:操作未完成,co_await 会暂停协程。 - ```await_suspend(handle)```:当操作未完成时调用,负责注册 “唤醒回调”。 - 参数 handle 是当前协程的句柄(std::coroutine_handle)。 - 返回值决定后续行为: - 返回 void:暂停当前协程,控制权返回给调用者。 - 返回 false:不暂停,继续执行当前协程。 - 返回另一个协程句柄:切换到该协程执行。 - ```await_resume()```:当操作完成、协程恢复时调用,返回异步操作的结果(或抛出异常)。   下面是一个简单的通过协程```await```异步等待的例子: ```cpp struct AsyncTimer { bool await_ready() { SPDLOG_INFO("await_ready"); return false; } void await_suspend(std::coroutine_handle<> handle) { SPDLOG_INFO("await_suspend"); std::thread([this, handle]() { std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms)); handle.resume(); // 延迟结束,恢复协程 }).detach(); } void await_resume() const noexcept { SPDLOG_INFO("await_resume"); } int delay_ms; }; struct Task { struct promise_type { Task get_return_object() { SPDLOG_INFO("get_return_object"); return Task{ std::coroutine_handle::from_promise(*this) }; } std::suspend_never initial_suspend() { SPDLOG_INFO("initial_suspend"); return {}; } // 立即执行 std::suspend_always final_suspend() noexcept { SPDLOG_INFO("final_suspend"); return {}; } // 最终暂停 void return_void() { SPDLOG_INFO("return_void"); } void unhandled_exception() { std::terminate(); } }; std::coroutine_handle handle; }; Task async_task() { SPDLOG_INFO("Task start"); SPDLOG_INFO("wait 1 seconds........"); co_await AsyncTimer{ 1000 }; // 等待1秒(异步操作) SPDLOG_INFO("wait 1 second done"); SPDLOG_INFO("wait another 1 seconds........"); co_await AsyncTimer{ 2000 }; // 再等待2秒 SPDLOG_INFO("wait 2 second done"); } int testAsyncTask() { auto task = async_task(); // 等待协程完成(简化处理,实际需更复杂的调度) SPDLOG_INFO("wait task done........"); while (!task.handle.done()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } SPDLOG_INFO("task done"); task.handle.destroy(); // 释放资源 return 0; } ```   上面的程序输出为,能够看到每次协程等待都会调用对应的```await_ready```、```await_suspend```、```await_resume```方法。并且我们的例子中没有添加```co_return```那是因为我们的例子不需要返回值,如果实际上需要的话还是要加上对应的```co_return```。 ```bash [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:262] get_return_object [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:266] initial_suspend [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:283] Task start [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:284] wait 1 seconds........ [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:239] await_ready [2025-07-24 08:56:34.288] [info] [SimpleCorontine.cpp:244] await_suspend [2025-07-24 08:56:34.289] [info] [SimpleCorontine.cpp:296] wait task done........ [2025-07-24 08:56:35.310] [info] [SimpleCorontine.cpp:252] await_resume [2025-07-24 08:56:35.311] [info] [SimpleCorontine.cpp:286] wait 1 second done [2025-07-24 08:56:35.311] [info] [SimpleCorontine.cpp:288] wait another 1 seconds........ [2025-07-24 08:56:35.311] [info] [SimpleCorontine.cpp:239] await_ready [2025-07-24 08:56:35.311] [info] [SimpleCorontine.cpp:244] await_suspend [2025-07-24 08:56:37.326] [info] [SimpleCorontine.cpp:252] await_resume [2025-07-24 08:56:37.327] [info] [SimpleCorontine.cpp:290] wait 2 second done [2025-07-24 08:56:37.327] [info] [SimpleCorontine.cpp:274] return_void [2025-07-24 08:56:37.327] [info] [SimpleCorontine.cpp:270] final_suspend [2025-07-24 08:56:37.356] [info] [SimpleCorontine.cpp:301] task done ``` ## 2 协程的分类 ### 2.1 有栈协程和无栈协程   协程根据栈类型主要分为有栈协程(Stackful Coroutines) 和无栈协程(Stackless Coroutines) 两种类型,它们在实现方式、内存占用和适用场景上有显著区别。   C++20 标准引入的无栈协程的核心特点在于,它们并不拥有独立的调用栈,而是依赖编译器生成的状态机来管理执行流程。这种协程的逻辑在编译时处理,运行时开销极低,因为它不需要为每个协程分配独立的栈空间,内存占用也相对较小(通常仅需保存少量状态变量)。编译器将协程代码转换为状态机,并通过 co_await、co_yield、co_return 等关键字控制暂停和恢复。无栈协程实际上是编译期的状态转换,编译器将协程的逻辑拆分为多个状态块,并通过状态变量追踪执行位置。因此,它的暂停和恢复本质上是状态的切换,而不需处理栈的保存与恢复。这使得切换效率极高,但也限制了它不能在普通函数中随意暂停(必须通过 co_await 等关键字显式标记暂停点)。   另一方面,有栈协程并不是 C++ 标准原生支持的特性,通常通过第三方库(如 Boost.Coroutine、libco 等)来实现。其核心特点在于拥有独立的调用栈,能够像普通函数一样自由调用其他函数。每个协程都有自己的栈,支持深度函数调用和递归,行为更接近“微型线程”。由于存在独立的栈空间,有栈协程通常需要预先分配栈内存(通常在 KB 级别),因此内存占用较高,创建和切换的开销也相对较大。由于不依赖编译器的特殊支持(依赖库的实现),有栈协程可以在 C++20 之前的版本中使用。有栈协程的运行时栈切换机制允许在任意函数调用中暂停协程(甚至在普通函数中),这使其行为更接近线程的特性,但也因此带来了额外的保存和恢复开销。 ### 2.2 对称协程和非对称协程   协程根据调度控制权转移方式,可分为对称协程(Symmetric Coroutines) 和非对称协程(Asymmetric Coroutines),这是与 “有栈 / 无栈” 维度正交的分类方式,核心区别在于协程之间如何进行切换。   非对称协程的核心特点是:协程的暂停和恢复存在明确的 “调用者 - 被调用者” 关系,即协程只能被它的调用者恢复,控制权转移路径是 “单向且层级化” 的。协程 A 创建并启动协程 B 后,B 的暂停只能返回到 A(它的直接调用者),而 B 无法直接切换到另一个无关协程 C。通常通过 `resume()`(恢复)和 `yield()`(暂停并返回调用者)两个操作控制,`yield()` 操作会明确将控制权交回给调用它的协程。由于调度关系明确,状态管理更简单,是大多数编程语言(如 C++20、Python、C#)采用的协程模型。   对称协程的核心特点是:所有协程地位平等,控制权可以在任意协程之间直接转移,没有固定的 “调用者 - 被调用者” 关系。任何协程可以通过 `transfer()` 等操作直接将控制权转移给另一个协程,无需经过中间调用者。需要一个全局调度器或协程管理器来协调所有协程的切换,灵活性更高。适合实现复杂的并发模型(如协作式多任务),但实现和调试难度较大。 ## 3 深入理解协程   上面谈到了协程的基本原理,但是协程的实现原理是比较复杂的,上面的例子只是一个简单的例子,实际上协程的实现原理是基于状态机的,每个协程在不同的状态下会调用不同的方法,并且协程的状态是可以切换的。协程的状态机模型使得协程能够在执行过程中挂起和恢复。每个协程都有一个内部状态,指示其当前执行位置。状态可以包括: - 初始状态:协程刚被创建,尚未开始执行。 - 挂起状态:协程执行到 ```co_await``` 或 ```co_yield``` 时挂起,等待某个事件或值。 - 完成状态:协程执行结束,所有操作完成。   状态机的状态切换主要通过```co_await```,```co_yield```和```resume```等操作配合控制。而为了更加精细的控制协程,C++20提供了```promise_type```,```promise_type```是协程中用于管理状态和结果的核心组件,可以让我们控制协程挂起,暂停,完成等状态切换时的动作。其中```co_await```和```promise_type```相对比较复杂,下面就展开描述下。 ### 3.1 协程句柄   协程句柄(coroutine handle)是一个指向协程的特殊对象,允许开发者控制协程的执行状态。它提供了一种机制,用于管理和恢复协程的执行。句柄能够用到的关键方法有```resume,destroy,promise```分别用来恢复协程,销毁协程和获取promise对象用来和协程交互。协程句柄大致的接口如下: ```cpp namespace std::experimental { template struct coroutine_handle; template<> struct coroutine_handle { bool done() const; void resume(); void destroy(); void* address() const; static coroutine_handle from_address(void* address); }; template struct coroutine_handle : coroutine_handle { Promise& promise() const; static coroutine_handle from_promise(Promise& promise); static coroutine_handle from_address(void* address); }; } ``` ### 3.2 ```co_await```   ```co_await```用于在协程中等待某个操作完成,它的作用是暂停协程的执行,等待操作完成后再恢复协程的执行。```co_await```后面的表达式需要是一个```Awaitable```对象。需要注意的是C++20实现中为了提高灵活性、可重用性和性能,将```co_await```接受的对象分为了```Awaitable```对象和```Awaiter```。 - ```Awaitable```:其类型实现了特定的接口,使其能与 co_await 关键字一起使用。Awaitable 对象能够在协程中被挂起,并在异步操作完成后恢复。如果运算符重载了```operator co_await```,当表达式使用 co_await 时,会尝试调用```operator co_await```。 - ```Awaiter```:实现了```await_ready```、```await_suspend```、```await_resume```三个方法的对象。```Awaiter```是一个与```Awaitable```相关的对象,负责处理协程的挂起和恢复逻辑。```Awaiter```提供了方法来管理协程的执行状态。   执行```co_await expr```表达式时,首先将```expr```转换成一个```Awaitable```对象,然后转换成```Awaiter```: 1. 构建```Awaitable```对象 1. 如果表达式是由初始挂起点、最终挂起点或 ```yield``` 表达式产生的,```Awaitable``` 就是该表达式本身。 2. 如果当前协程的 ```Promise``` 类型具有 ```await_transform``` 成员函数,```Awaitable``` 将是 ```promise.await_transform(expr)``` 的结果。 3. 如果不满足以上条件,```Awaitable``` 就是该表达式本身。 2. 构建```Awaiter```对象。根据```Awaitable```对象构造```Awaiter```对象。 1. 如果 ```operator co_await``` 的重载解析为单一最佳重载,```Awaiter``` 就是该调用的结果。 1. 对于成员重载,使用 ```awaitable.operator co_await()```。 2. 对于非成员重载,使用 ```operator co_await(static_cast(awaitable))```。 2. 如果没有找到合适的重载,```Awaiter```就是 ```Awaitable``` 本身。   上述过程大致伪代码如下: ```cpp template decltype(auto) get_awaitable(P& promise, T&& expr) { if constexpr (has_any_await_transform_member_v

) return promise.await_transform(static_cast(expr)); else return static_cast(expr); } template decltype(auto) get_awaiter(Awaitable&& awaitable) { if constexpr (has_member_operator_co_await_v) return static_cast(awaitable).operator co_await(); else if constexpr (has_non_member_operator_co_await_v) return operator co_await(static_cast(awaitable)); else return static_cast(awaitable); } ```   获取```Awaiter```之后就可以根据其定义的```await_suspend```等实现来对协程进行控制。 ```cpp { auto&& value = ; auto&& awaitable = get_awaitable(promise, static_cast(value)); auto&& awaiter = get_awaiter(static_cast(awaitable)); if (!awaiter.await_ready()) { using handle_t = std::experimental::coroutine_handle

; using await_suspend_result_t = decltype(awaiter.await_suspend(handle_t::from_promise(promise))); if constexpr (std::is_void_v) { awaiter.await_suspend(handle_t::from_promise(promise)); } else { static_assert( std::is_same_v, "await_suspend() must return 'void' or 'bool'."); if (awaiter.await_suspend(handle_t::from_promise(promise))) { } } } return awaiter.await_resume(); } ```   下面写一个简单的例子来展示```Awaitable```和```Awaiter```对象构造过程。 ```cpp class MyAwaiter { public: bool await_ready() const noexcept { SPDLOG_INFO("Awaiter: Checking if ready"); return false; } void await_suspend(std::coroutine_handle<>) { SPDLOG_INFO("Awaiter: Coroutine suspended"); } int await_resume() { SPDLOG_INFO("Awaiter: Resuming coroutine"); return 42; } }; // Awaitable 类 class MyAwaitable { public: MyAwaitable(std::string v) { SPDLOG_INFO("MyAwaitable::MyAwaitable"); } MyAwaiter operator co_await() { SPDLOG_INFO("Awaitable: Co-await called"); return MyAwaiter(); // 返回 Awaiter 对象 } }; // 协程示例 struct MyCoroutine { struct promise_type { MyCoroutine get_return_object() { SPDLOG_INFO("get_return_object"); return MyCoroutine{}; } auto initial_suspend() noexcept { SPDLOG_INFO("initial_suspend"); return std::suspend_never{}; } auto final_suspend() noexcept { SPDLOG_INFO("final_suspend"); return std::suspend_never{}; } void return_void() {} void unhandled_exception() {} template auto await_transform(T expr) { SPDLOG_INFO("Awaitable: await_transform called"); return MyAwaitable(""); // 返回 Awaiter 对象 } }; }; MyCoroutine start() { SPDLOG_INFO("Coroutine started"); co_await ""; // 使用 Awaitable SPDLOG_INFO("Coroutine resumed"); co_return; } void testAwaiter(){ SPDLOG_INFO("testAwaiter started"); auto corn = start(); SPDLOG_INFO("testAwaiter end"); } ```   上面的代码输出如下,和上面描述的流程完全一致。 ```cpp [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:371] testAwaiter started [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:341] get_return_object [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:345] initial_suspend [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:364] Coroutine started [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:357] Awaitable: await_transform called [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:328] MyAwaitable::MyAwaitable [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:332] Awaitable: Co-await called [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:310] Awaiter: Checking if ready [2025-07-24 22:57:41.604] [info] [SimpleCorontine.cpp:315] Awaiter: Coroutine suspended [2025-07-24 22:57:41.605] [info] [SimpleCorontine.cpp:373] testAwaiter end ```   根据上面的流程可以看出,我们可以根据```await```的参数来构造```Awaitable```对象从而获得```Awaiter```,这样就给我们控制协程流程提供了遍历,我们可以通过```Awaitable```对我们的逻辑进行封装,可以不同情况使用不同的```Awaiter```使得逻辑更加清晰,更加可扩展。 ### 3.3 ```promise_type```   协程的另一个重点是```promise_type```,```promise_type```是一个协程状态控制器,用于定义协程的行为,包括协程的返回值、异常处理、协程的挂起和恢复等。任何一个协程必须包含```promise_type```,否则无法通过编译。当我们实现了一个协程的```promise_type```之后,其运行的大致流程如下: ```cpp { co_await promise.initial_suspend(); try { } catch (...) { promise.unhandled_exception(); } FinalSuspend: co_await promise.final_suspend(); } ```   编译器决定```promise_type```的类型,是根据```coroutine_traits```来获取的,我们可以通过下面方式获取到对应协程的```promise_typ```,需要注意的是协程的参数列表要和模板的列表对应上。 ```cpp UserAllocCoroutine userAllocCoroutine(MyClass cls, MyClass cls2) { SPDLOG_INFO("Coroutine started."); co_return; SPDLOG_INFO("Coroutine resumed."); } void testPromiseType(){ using promise_type = std::coroutine_traits::promise_type; const auto name = std::string(typeid(promise_type).name()); SPDLOG_INFO("promise type {}", name); } ```   上面只是大体的流程,实际的执行流程有很多细节: 1. 使用```operator new``` 分配协程帧(可选,由编译器实现)。 2. 将所有函数参数复制到协程帧中。 3. 调用类型为 P 的```promise_type```的构造函数。 4. 调用 ```promise.get_return_object()``` 方法获取协程首次暂停时返回给调用者的结果,并将其保存为局部变量。 5. 调用 ```promise.initial_suspend()``` 方法并 ```co_await``` 其结果。 6. 当 ```co_await promise.initial_suspend()``` 表达式恢复执行(无论是立即恢复还是异步恢复)时,协程开始执行你编写的函数体语句。 7. 重复6步骤,直到执行到```co_return```: 1. 调用 ```promise.return_void()``` 或 ```promise.return_value()```。 2. 按创建顺序的逆序销毁所有自动存储期变量。 3. 调用 ```promise.final_suspend()``` 并 ```co_await``` 其结果。   当执行过程中发生未被捕获的异常时,会触发```unhandled_exception```: 1. 捕获异常,并在 catch 块中调用 ```promise.unhandled_exception()```。 2. 调用 ```promise.final_suspend()``` 并 ```co_await``` 其结果。   从上面的流程能够看出,协程的运行基本上都是通过```promise_type```进行控制的。 ### 3.4 协程帧   函数的执行有栈帧来保存现场恢复现场,对应的协程有协程帧在执行时用来保存其局部状态、局部变量、调用栈以及其他上下文信息的结构。和函数栈帧类似,协程帧也有其相关的创建和销毁流程,相关时机自然不用说分别在协程的调用开头和协程结束点。 **协程帧创建和销毁**   协程帧通过非数组形式的```operator new```动态分配内存。如果```Promise type```定义了类级别的 ```operator new```重载,则使用这个重载进行分配;否则,将使用全局 ```operator new```。但是需要注意的是传递给 ```operator new``` 的大小不是 ```sizeof(P)```,而是整个协程帧的大小。编译器会根据参数数量和大小、```promise_type```大小、局部变量数量和大小,以及管理协程状态所需的其他编译器特定存储,自动计算该大小。同时若能确定协程帧的生命周期严格嵌套在调用者的生命周期内且在调用点能确定协程帧所需的大小,编译器也会根据优化策略选择省略```operator new```调用。申请内存有可能会失败,针对该情况,若```promise_type```提供了静态成员函数 ```P::get_return_object_on_allocation_failure()```,编译器会转而调用 ```operator new(size_t, nothrow_t)``` 重载。若该调用返回 ```nullptr```,协程会立即调用 ```P::get_return_object_on_allocation_failure()```,并将结果返回给调用者,而非抛出异常。   下面的例子通过重载了```operator new/delete```操作来hook创建协程帧的动作。 ```cpp struct UserAllocCoroutine { struct UserAllocPromise { UserAllocPromise(){ SPDLOG_INFO("UserAllocPromise constructed."); } // 自定义的 operator new void* operator new(std::size_t size) { SPDLOG_INFO("Custom operator new called, size: {} sizeof(UserAllocCoroutine) = {}", size, sizeof(UserAllocCoroutine)); return ::operator new(size); // 调用全局 operator new } // 自定义的 operator delete void operator delete(void* ptr) noexcept { SPDLOG_INFO("Custom operator delete called."); ::operator delete(ptr); // 调用全局 operator delete } // 协程返回对象 auto get_return_object() { SPDLOG_INFO("get_return_object called."); return UserAllocCoroutine{ std::coroutine_handle::from_promise(*this) }; } // 处理内存分配失败 static auto get_return_object_on_allocation_failure() { SPDLOG_INFO("Allocation failed, returning alternative object."); std::terminate(); return UserAllocCoroutine{}; // 返回一个默认构造的协程 } // 初始挂起 auto initial_suspend() noexcept { SPDLOG_INFO("initial_suspend called."); return std::suspend_always{}; } // 最终挂起 auto final_suspend() noexcept { SPDLOG_INFO("final_suspend called."); return std::suspend_always{}; } void return_void() { SPDLOG_INFO("return_void called."); } void unhandled_exception() { SPDLOG_INFO("unhandled_exception called."); std::exit(1); } }; using promise_type = UserAllocPromise; std::coroutine_handle handle; UserAllocCoroutine(std::coroutine_handle h) : handle(h) { SPDLOG_INFO("UserAllocCoroutine constructed."); } UserAllocCoroutine() : handle(nullptr) { SPDLOG_INFO("UserAllocCoroutine default constructed."); } ~UserAllocCoroutine() { if (handle) { SPDLOG_INFO("Destroying coroutine."); handle.destroy(); } } }; // 协程函数 UserAllocCoroutine userAllocCoroutine() { SPDLOG_INFO("Coroutine started."); co_return; SPDLOG_INFO("Coroutine resumed."); } int testUserAlloc() { spdlog::set_level(spdlog::level::info); // 设置日志级别 auto coroutine = userAllocCoroutine(); // 启动协程 coroutine.handle.resume(); // 恢复协程 return 0; } ```   对应的输出如下,可以看到```new/delete```分别是在协程开始和结束时调用的。同时能够看到协程帧的大小。 ```bash [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:385] Custom operator new called, size: 432 sizeof(UserAllocCoroutine) = 8 [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:380] UserAllocPromise constructed. [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:397] get_return_object called. [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:434] UserAllocCoroutine constructed. [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:410] initial_suspend called. [2025-07-25 20:18:50.618] [info] [SimpleCorontine.cpp:451] Coroutine started. [2025-07-25 20:18:50.619] [info] [SimpleCorontine.cpp:421] return_void called. [2025-07-25 20:18:50.619] [info] [SimpleCorontine.cpp:416] final_suspend called. [2025-07-25 20:18:50.619] [info] [SimpleCorontine.cpp:443] Destroying coroutine. [2025-07-25 20:18:50.619] [info] [SimpleCorontine.cpp:391] Custom operator delete called. ``` **参数复制到协程帧**   协程帧的参数复制规则和函数调用的参数复制规则类似,如果期望将参数传递给```promise_type```,只需要在```promise_type```构造函数中添加期望传递的参数即可。同时需要考虑参数的生命周期确保协程访问期间其生命周期是确定的: - 若参数按值传递,则通过调用该类型的移动构造函数将参数复制到协程帧。 - 若参数按引用传递(左值引用或右值引用),则仅将引用复制到协程帧,而非引用指向的值。 ```cpp struct MyClass{ MyClass() { SPDLOG_INFO("MyClass::MyClass"); } std::string name = ""; }; struct UserAllocCoroutine { struct UserAllocPromise { MyClass cls; UserAllocPromise(MyClass cls, MyClass cls2){ SPDLOG_INFO("UserAllocPromise constructed."); cls = cls; } //省略部分代码 }; //省略部分代码 }; UserAllocCoroutine userAllocCoroutine(MyClass cls, MyClass cls2) { SPDLOG_INFO("Coroutine started."); co_return; SPDLOG_INFO("Coroutine resumed."); } ``` ### 3.5 更深入理解协程   之前对于协程的不同操作符等进行了简单的描述,为了更加深入理解协程的运作方式,本节将通过伪代码来描述不同操作对应的等效代码。假设有以下场景: ```cpp class task { public: struct awaiter; class promise_type { public: promise_type() noexcept; ~promise_type(); struct final_awaiter { bool await_ready() noexcept; std::coroutine_handle<> await_suspend( std::coroutine_handle h) noexcept; void await_resume() noexcept; }; task get_return_object() noexcept; std::suspend_always initial_suspend() noexcept; final_awaiter final_suspend() noexcept; void unhandled_exception() noexcept; void return_value(int result) noexcept; private: friend task::awaiter; std::coroutine_handle<> continuation_; std::variant result_; }; task(task&& t) noexcept; ~task(); task& operator=(task&& t) noexcept; struct awaiter { explicit awaiter(std::coroutine_handle h) noexcept; bool await_ready() noexcept; std::coroutine_handle await_suspend( std::coroutine_handle<> h) noexcept; int await_resume(); private: std::coroutine_handle coro_; }; awaiter operator co_await() && noexcept; private: explicit task(std::coroutine_handle h) noexcept; std::coroutine_handle coro_; }; task g(int x) { int fx = co_await f(x); co_return fx * fx; } ```     当编译器发现函数包含三个协程关键字(```co_await```、```co_yield```或```co_return```)中的任何一个时,就会开始协程转换过程。其转换的基本步骤如下面描述。 **确定promise_type**   第一步是通过将签名的返回类型和参数类型作为模板参数代入std::coroutine_traits类型来确定的promise_type。 ```cpp using __g_promise_t = std::coroutine_traits::promise_type; ``` **创建协程state**   协程函数需要在暂停时保存协程的状态、参数和局部变量,以便在后续恢复时仍可访问。协程状态包含以下几部分: - ```promise_type```(promise object) - 所有函数参数的副本 - 关于当前暂停点的信息以及如何恢复 / 销毁协程 - 生命周期跨越暂停点的局部变量 / 临时对象的存储   上面提到过```promise_type```的构造过程,编译器会首先尝试用参数副本的左值引用来调用```promise_type```构造函数(如果有效),否则回退到调用```promise_type```的默认构造函数。这里不再赘述,下面是一个简单的辅助函数来描述该过程。 ```cpp template Promise construct_promise([[maybe_unused]] Params&... params) { if constexpr (std::constructible_from) { return Promise(params...); } else { return Promise(); } } ```   基于此,我们添加一个简单的带构造函数的```__g_state```来描述协程状态。 ```cpp struct __g_state { __g_state(int&& x) : x(static_cast(x)) , __promise(construct_promise<__g_promise_t>(this->x)) {} int x; __g_promise_t __promise; // 待填充 }; ```   进入协程之后,救护创建协程state用来控制协程,如果没有定义```operator new```则直接走默认的全局```new```,否则使用对应的重载,下面就是具体的过程。和之前描述的对齐,失败时转到```get_return_object_on_allocation_failure```处理分配错误。 ```cpp template void* __promise_allocate(std::size_t size, [[maybe_unused]] Args&... args) { if constexpr (requires { Promise::operator new(size, args...); }) { return Promise::operator new(size, args...); } else { return Promise::operator new(size); } } task g(int x) { void* state_mem = __promise_allocate<__g_promise_t>(sizeof(__g_state), x); __g_state* state; try { state = ::new (state_mem) __g_state(static_cast(x)); if (state == nullptr) { return __g_promise_t::get_return_object_on_allocation_failure(); } } catch (...) { __g_promise_t::operator delete(state_mem); throw; } // ... 实现启动函数的其余部分 } ``` **创建返回对象**   创建协程state之后就是调用```get_return_object```获取返回值,这个返回值被存储为局部变量,并在启动函数的最后(完成其他步骤后)返回。我们将上面伪代码中的```operator new```重载全部替换为全局```new```来简化逻辑,方便查阅。 ```cpp task g(int x) { std::unique_ptr<__g_state> state(new __g_state(static_cast(x))); decltype(auto) return_value = state->__promise.get_return_object(); // ... 实现启动函数的其余部分 return return_value; } ``` **初始暂停点**   启动函数在调用```get_return_object()```之后要做的是开始执行协程体,而协程体中要执行的第一件事是初始暂停点,即求值```co_await promise.initial_suspend()```。由于从```initial_suspend()```和(可选的)```operator co_await()```返回的对象的生命周期会跨越暂停点(它们在协程暂停之前创建,在恢复之后销毁),这些对象的存储需要放在协程状态中。那考虑如果求值过程中发生了异常,那么: - 以下情况发生的异常会传播回启动函数的调用者,并且协程状态会被自动销毁: - ```initial_suspend()```的调用 - 对返回的可等待对象的```operator co_await()```调用(如果已定义) - 等待体的```await_ready()```调用 - 等待体的```await_suspend()```调用 - 以下场景发生的异常会被协程体捕获,并调用```promise.unhandled_exception()```: - ```await_resume()```的调用 - 从```operator co_await()```返回的对象的析构函数(如适用) - 从```initial_suspend()```返回的对象的析构函数   虽然上面的例子中初始化使用的```initial_suspend()```返回的是```std::suspend_always```,但是如果返回的其他可等待类型,那就有可能发生上面描述的情况。因此需要在协程状态中为它保留存储来控制生命周期,这里用```suspend_always```做示例添加一个```manual_lifetime```它是可平凡构造和可平凡析构的,但允许我们在需要时显式构造 / 析构存储的值。 ```cpp template struct manual_lifetime { manual_lifetime() noexcept = default; ~manual_lifetime() = default; // 不可复制/移动 manual_lifetime(const manual_lifetime&) = delete; manual_lifetime(manual_lifetime&&) = delete; manual_lifetime& operator=(const manual_lifetime&) = delete; manual_lifetime& operator=(manual_lifetime&&) = delete; template requires std::invocable && std::same_as, T> T& construct_from(Factory factory) noexcept(std::is_nothrow_invocable_v) { return *::new (static_cast(&storage)) T(factory()); } void destroy() noexcept(std::is_nothrow_destructible_v) { std::destroy_at(std::launder(reinterpret_cast(&storage))); } T& get() & noexcept { return *std::launder(reinterpret_cast(&storage)); } private: alignas(T) std::byte storage[sizeof(T)]; }; ```   基于此在```__g_state```中添加对应的数据成员。 ```cpp struct __g_state { __g_state(int&& x); int x; __g_promise_t __promise; manual_lifetime __tmp1; // 待填充 }; ```   一旦我们通过调用```intial_suspend()```构造了这个对象,我们就需要调用三个方法来实现```co_await```表达式:```await_ready()```、```await_suspend()```和```await_resume()```。调用```await_suspend()```时,我们需要向它传递当前协程的句柄。目前,我们可以只调用```std::coroutine_handle<__g_promise_t>::from_promise()```并传递对该```promise_type```的引用。稍后我们会详细了解其内部工作原理。 ```cpp task g(int x) { std::unique_ptr<__g_state> state(new __g_state(static_cast(x))); decltype(auto) return_value = state->__promise.get_return_object(); state->__tmp1.construct_from([&]() -> decltype(auto) { return state->__promise.initial_suspend(); }); if (!state->__tmp1.get().await_ready()) { // // ... 在这里暂停协程 // state->__tmp1.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); state.release(); // 向下执行到下面的return语句 } else { // 协程没有暂停 state.release(); // // ... 开始执行协程体 // } return return_value; } ``` **记录暂停点**   当协程暂停时,它需要确保在恢复时能回到暂停时的控制流位置。它还需要跟踪每个暂停点处哪些自动存储期对象处于活动状态,以便知道如果协程被销毁(而不是恢复)时需要销毁什么。实现这一点的一种方法是为协程中的每个暂停点分配一个唯一编号,并将其存储在协程状态的整数数据成员中。然后,每当协程暂停时,它会将暂停点的编号写入协程状态;当它被恢复 / 销毁时,我们会检查这个整数,看看它暂停在哪个暂停点。因此,我们扩展协程状态,添加一个整数数据成员来存储暂停点索引,并将其初始化为 0,只需要在适当的时机更新该暂停点的值即可: ```cpp struct __g_state { __g_state(int&& x); int x; __g_promise_t __promise; int __suspend_point = 0; // <-- 添加暂停点索引 manual_lifetime __tmp1; // 待填充 }; ``` **实现coroutine_handle::resume()和coroutine_handle::destroy()**   调用```resume```和```destroy```都会导致协程体的执行,只是```resume```会在暂停点恢复执行,而```destroy```会直接跳转到协程体的结束。在实现 C++ 协程的```coroutine_handle``` 类型时,我们需要通过类型擦除的方式存储协程状态的恢复和销毁函数指针,以支持对任意协程实例的管理。这种设计使得 ```coroutine_handle``` 只包含一个指向协程状态的指针,并通过状态对象中的函数指针进行恢复和销毁操作,同时提供方法在 ```void*``` 和具体状态之间转换。   此外,为了确保函数指针的布局在所有协程状态类型中保持一致,我们可以让每个协程状态类型继承自一个包含这些数据成员的基类。这种方法使得协程能够通过任何指向该协程的句柄进行恢复和销毁,而不仅限于最近一次调用时传递的句柄。 ```cpp struct __coroutine_state { using __resume_fn = void(__coroutine_state*); using __destroy_fn = void(__coroutine_state*); __resume_fn* __resume; __destroy_fn* __destroy; }; ```   在协程```handle```的```resume```中只需要调用函数指针即可。 ```cpp namespace std { template class coroutine_handle; template<> class coroutine_handle { public: coroutine_handle() noexcept = default; coroutine_handle(const coroutine_handle&) noexcept = default; coroutine_handle& operator=(const coroutine_handle&) noexcept = default; void* address() const { return static_cast(state_); } static coroutine_handle from_address(void* ptr) { coroutine_handle h; h.state_ = static_cast<__coroutine_state*>(ptr); return h; } explicit operator bool() noexcept { return state_ != nullptr; } friend bool operator==(coroutine_handle a, coroutine_handle b) noexcept { return a.state_ == b.state_; } void resume() const { state_->__resume(state_); } void destroy() const { state_->__destroy(state_); } bool done() const { return state_->__resume == nullptr; } private: __coroutine_state* state_ = nullptr; }; } ``` **实现coroutine_handle::promise()和from_promise()**   对于更通用的```coroutine_handle```特化,大多数实现可以直接复用```coroutine_handle```的实现。然而,我们还需要能够访问协程状态的```promise_type```(通过```promise()```方法返回),以及能从```promise_type```的引用构造```coroutine_handle```。因此,我们需要定义一个新的协程状态基类,它继承自```__coroutine_state```并包含```promise_type```,以便我们可以定义所有使用特定```promise_type```的协程状态类型都继承自这个基类。同时,由于```promise_type```的构造函数可能需要传递参数副本的引用,我们需要```promise_type```的构造函数在参数副本的构造函数之后调用。因此我们在这个基类中为```promise_type```预留存储,使其相对于协程状态的起始位置有一个固定的偏移量,但让派生类负责在参数副本初始化后的适当位置调用构造函数 / 析构函数来实现类似的控制。 ```cpp template struct __coroutine_state_with_promise : __coroutine_state { __coroutine_state_with_promise() noexcept {} ~__coroutine_state_with_promise() {} union { Promise __promise; }; }; ```   然后更新```__g_state```类,使其继承自这个新基类: ```cpp struct __g_state : __coroutine_state_with_promise<__g_promise_t> { __g_state(int&& __x) : x(static_cast(__x)) { // 使用 placement-new 在基类中初始化承诺对象 ::new ((void*)std::addressof(this->__promise)) __g_promise_t(construct_promise<__g_promise_t>(x)); } ~__g_state() { // 还需要在参数对象销毁前手动调用承诺析构函数 this->__promise.~__g_promise_t(); } int __suspend_point = 0; int x; manual_lifetime __tmp1; // 待填充 }; ```   有了上面的基础,就可以定义```std::coroutine_handle```类模板了: ```cpp namespace std { template class coroutine_handle { using state_t = __coroutine_state_with_promise; public: coroutine_handle() noexcept = default; coroutine_handle(const coroutine_handle&) noexcept = default; coroutine_handle& operator=(const coroutine_handle&) noexcept = default; operator coroutine_handle() const noexcept { return coroutine_handle::from_address(address()); } explicit operator bool() const noexcept { return state_ != nullptr; } friend bool operator==(coroutine_handle a, coroutine_handle b) noexcept { return a.state_ == b.state_; } void* address() const { return static_cast(static_cast<__coroutine_state*>(state_)); } static coroutine_handle from_address(void* ptr) { coroutine_handle h; h.state_ = static_cast(static_cast<__coroutine_state*>(ptr)); return h; } Promise& promise() const { return state_->__promise; } static coroutine_handle from_promise(Promise& promise) { coroutine_handle h; // 我们知道__promise成员的地址,因此通过从该地址减去__promise字段的偏移量来计算协程状态的地址 h.state_ = reinterpret_cast( reinterpret_cast(std::addressof(promise)) - offsetof(state_t, __promise)); return h; } // 用coroutine_handle的实现来定义这些 void resume() const { static_cast>(*this).resume(); } void destroy() const { static_cast>(*this).destroy(); } bool done() const { return static_cast>(*this).done(); } private: state_t* state_; }; } ``` **协程体的开端**   先向前声明正确签名的恢复 / 销毁函数,并更新```__g_state```构造函数以初始化协程状态,使恢复 / 销毁函数指针指向它们: ```cpp void __g_resume(__coroutine_state* s); void __g_destroy(__coroutine_state* s); struct __g_state : __coroutine_state_with_promise<__g_promise_t> { __g_state(int&& __x) : x(static_cast(__x)) { // 初始化coroutine_handle方法使用的函数指针 this->__resume = &__g_resume; this->__destroy = &__g_destroy; // 使用placement-new在基类中初始化承诺对象 ::new ((void*)std::addressof(this->__promise)) __g_promise_t(construct_promise<__g_promise_t>(x)); } // ... 其余部分省略以简洁起见 }; task g(int x) { std::unique_ptr<__g_state> state(new __g_state(static_cast(x))); decltype(auto) return_value = state->__promise.get_return_object(); state->__tmp1.construct_from([&]() -> decltype(auto) { return state->__promise.initial_suspend(); }); if (!state->__tmp1.get().await_ready()) { state->__tmp1.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); state.release(); // 向下执行到下面的return语句 } else { // 协程没有暂停。立即开始执行体 __g_resume(state.release()); } return return_value; } ```   ```resume/destroy```两个函数差不多都是根据暂停点索引生成跳转到代码中正确位置的跳转表,区别只是前者需要主动恢复协程,后者要销毁对应的数据和状态。 ```cpp void __g_resume(__coroutine_state* s) { // 我们知道's'指向__g_state auto* state = static_cast<__g_state*>(s); // 根据暂停点索引生成跳转到代码中正确位置的跳转表 switch (state->__suspend_point) { case 0: goto suspend_point_0; default: std::unreachable(); } suspend_point_0: state->__tmp1.get().await_resume(); state->__tmp1.destroy(); // TODO: 实现协程体的其余部分 // // int fx = co_await f(x); // co_return fx * fx; } void __g_destroy(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); switch (state->__suspend_point) { case 0: goto suspend_point_0; default: std::unreachable(); } suspend_point_0: state->__tmp1.destroy(); goto destroy_state; // TODO: 为其他暂停点添加额外逻辑 destroy_state: delete state; } ``` **co_await表达式**   对于```co_await```首先需要求值,我们的场景中首先需要求值```f(x)```,它返回一个临时的```task```对象。由于临时```task```直到语句末尾的分号才会被销毁,且该语句包含```co_await```表达式,因此task的生命周期跨越了暂停点,因此它必须存储在协程状态中。当对这个临时```task```求值```co_await```表达式时,我们需要调用```operator co_await()```方法,该方法返回一个临时的```awaiter```对象。这个对象的生命周期也跨越了暂停点,因此也必须存储在协程状态中。 ```cpp struct __g_state : __coroutine_state_with_promise<__g_promise_t> { __g_state(int&& __x); ~__g_state(); int __suspend_point = 0; int x; manual_lifetime __tmp1; manual_lifetime __tmp2; manual_lifetime __tmp3; }; ```   既然添加了```__tmp2```和```__tmp3```,我们需要在```__g_destroy```函数中添加对应的销毁逻辑。同时,注意```task::awaiter::await_suspend()```方法返回一个协程句柄,因此我们需要生成代码来恢复返回的句柄。我们还需要在调用```await_suspend()```之前更新暂停点索引(我们将为此暂停点使用索引 1),然后在跳转表中添加一个额外的条目,确保我们能回到正确的位置恢复。 ```cpp void __g_resume(__coroutine_state* s) { // 我们知道's'指向__g_state auto* state = static_cast<__g_state*>(s); // 根据暂停点索引生成跳转到代码中正确位置的跳转表 switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; // <-- 添加新的跳转表条目 default: std::unreachable(); } suspend_point_0: state->__tmp1.get().await_resume(); state->__tmp1.destroy(); // int fx = co_await f(x); state->__tmp2.construct_from([&] { return f(state->x); }); state->__tmp3.construct_from([&] { return static_cast(state->__tmp2.get()).operator co_await(); }); if (!state->__tmp3.get().await_ready()) { // 标记暂停点 state->__suspend_point = 1; auto h = state->__tmp3.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); // 在返回前恢复返回的协程句柄 h.resume(); return; } suspend_point_1: int fx = state->__tmp3.get().await_resume(); state->__tmp3.destroy(); state->__tmp2.destroy(); // TODO: 实现 // co_return fx * fx; } void __g_destroy(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; // <-- 添加新的跳转表条目 default: std::unreachable(); } suspend_point_0: state->__tmp1.destroy(); goto destroy_state; suspend_point_1: state->__tmp3.destroy(); state->__tmp2.destroy(); goto destroy_state; // TODO: 为其他暂停点添加额外逻辑 destroy_state: delete state; } ``` **实现unhandled_exception()**   协程的行为就像其函数体被替换为: ```cpp { promise-type promise promise-constructor-arguments ; try { co_await promise.initial_suspend() ; function-body } catch ( ... ) { if (!initial-await-resume-called) throw ; promise.unhandled_exception() ; } final-suspend : co_await promise.final_suspend() ; } ```   我们已经在启动函数中单独处理了```initial-await_resume-called```分支,需要处理```resume/destroy```抛出的异常。如果从返回的协程的```.resume()```调用中抛出异常,它不应被当前协程捕获,而应传播出恢复此协程的```resume()```调用。因此,我们将协程句柄存储在函数顶部声明的变量中,然后```goto```到 ```try/catch``` 之外的点,并在那里执行```.resume()```调用。 ```cpp void __g_resume(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); std::coroutine_handle coro_to_resume; try { switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; // <-- 添加新的跳转表条目 default: std::unreachable(); } suspend_point_0: state->__tmp1.get().await_resume(); state->__tmp1.destroy(); // int fx = co_await f(x); state->__tmp2.construct_from([&] { return f(state->x); }); state->__tmp3.construct_from([&] { return static_cast(state->__tmp2.get()).operator co_await(); }); if (!state->__tmp3.get().await_ready()) { state->__suspend_point = 1; coro_to_resume = state->__tmp3.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); goto resume_coro; } suspend_point_1: int fx = state->__tmp3.get().await_resume(); state->__tmp3.destroy(); state->__tmp2.destroy(); // TODO: 实现 // co_return fx * fx; } catch (...) { state->__promise.unhandled_exception(); goto final_suspend; } final_suspend: // TODO: 实现 // co_await promise.final_suspend(); resume_coro: coro_to_resume.resume(); return; } ```   然而,上面的代码存在一个错误。如果```__tmp3.get().await_resume()```调用抛出异常,我们将无法在捕获异常之前调用```__tmp3```和```__tmp2```的析构函数。注意,我们不能简单地捕获异常、调用析构函数然后重新抛出异常,因为这会改变那些析构函数的行为 —— 如果它们调用```std::unhandled_exceptions()```,由于异常已被 “处理”,返回值会不同。然而,如果析构函数在异常展开期间调用它,```std::unhandled_exceptions()```的调用应该返回非零值。相反,我们可以定义一个 RAII 辅助类,确保在抛出异常时在作用域退出时调用析构函数。 ```cpp template struct destructor_guard { explicit destructor_guard(manual_lifetime& obj) noexcept : ptr_(std::addressof(obj)) {} // 不可移动 destructor_guard(destructor_guard&&) = delete; destructor_guard& operator=(destructor_guard&&) = delete; ~destructor_guard() noexcept(std::is_nothrow_destructible_v) { if (ptr_ != nullptr) { ptr_->destroy(); } } void cancel() noexcept { ptr_ = nullptr; } private: manual_lifetime* ptr_; }; // 对不需要调用析构函数的类型的部分特化 template requires std::is_trivially_destructible_v struct destructor_guard { explicit destructor_guard(manual_lifetime&) noexcept {} void cancel() noexcept {} }; // 类模板参数推导以简化使用 template destructor_guard(manual_lifetime& obj) -> destructor_guard; void __g_resume(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); std::coroutine_handle coro_to_resume; try { switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; // <-- 添加新的跳转表条目 default: std::unreachable(); } suspend_point_0: { destructor_guard tmp1_dtor{state->__tmp1}; state->__tmp1.get().await_resume(); } // int fx = co_await f(x); { state->__tmp2.construct_from([&] { return f(state->x); }); destructor_guard tmp2_dtor{state->__tmp2}; state->__tmp3.construct_from([&] { return static_cast(state->__tmp2.get()).operator co_await(); }); destructor_guard tmp3_dtor{state->__tmp3}; if (!state->__tmp3.get().await_ready()) { state->__suspend_point = 1; coro_to_resume = state->__tmp3.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); // 协程暂停时不退出作用域 // 因此取消析构保护 tmp3_dtor.cancel(); tmp2_dtor.cancel(); goto resume_coro; } // 不要在这里退出作用域 // // 我们不能'goto'到进入具有非平凡析构函数的变量作用域的标签 // 因此我们必须在不调用析构函数的情况下退出析构保护的作用域,然后在`suspend_point_1`标签后重新创建它们 tmp3_dtor.cancel(); tmp2_dtor.cancel(); } suspend_point_1: int fx = [&]() -> decltype(auto) { destructor_guard tmp2_dtor{state->__tmp2}; destructor_guard tmp3_dtor{state->__tmp3}; return state->__tmp3.get().await_resume(); }(); // TODO: 实现 // co_return fx * fx; } catch (...) { state->__promise.unhandled_exception(); goto final_suspend; } final_suspend: // TODO: 实现 // co_await promise.final_suspend(); resume_coro: coro_to_resume.resume(); return; } ```   对于```promise.unhandled_exception()```方法本身抛出异常的情况(例如,如果它重新抛出当前异常),可能需要特殊处理。这种情况下,协程需要捕获异常,将协程标记为在最终暂停点暂停,然后重新抛出异常。 ```cpp __g_resume(){ //省略部分代码............ try { // ... } catch (...) { try { state->__promise.unhandled_exception(); } catch (...) { state->__suspend_point = 2; state->__resume = nullptr; // 标记为最终暂停点 throw; } } //省略部分代码............ } __g_destroy(){ //省略部分代码............ switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; case 2: goto destroy_state; // 没有需要销毁的作用域内变量 // 只需销毁协程状态对象 } //省略部分代码............ } ``` **实现co_return**   ```co_return ```实现相对简单: ```cpp state->__promise.return_value(fx * fx); goto final_suspend; ``` **实现final_suspend()**   ```final_suspend()```方法返回一个临时的```task::promise_type::final_awaiter```类型,需要将其存储在协程状态中,并在```__g_destroy```中销毁。这种类型没有自己的```operator co_await()```,因此我们不需要为该调用的结果准备额外的临时对象。与```task::awaiter```类型一样,它也使用返回协程句柄的```await_suspend()```形式。因此,我们需要确保对返回的句柄调用```resume()```。如果协程不在最终暂停点暂停,则协程状态会被隐式销毁。因此,如果执行到达协程末尾,我们需要删除状态对象。此外,由于所有最终暂停逻辑都要求是 ```noexcept``` 的,不需要担心任何子表达式会抛出异常。 ```cpp struct __g_state : __coroutine_state_with_promise<__g_promise_t> { __g_state(int&& __x); ~__g_state(); int __suspend_point = 0; int x; manual_lifetime __tmp1; manual_lifetime __tmp2; manual_lifetime __tmp3; manual_lifetime __tmp4; // <--- }; ```   ```final_suspend()```的实现: ```cpp final_suspend: // co_await promise.final_suspend { state->__tmp4.construct_from([&]() noexcept { return state->__promise.final_suspend(); }); destructor_guard tmp4_dtor{state->__tmp4}; if (!state->__tmp4.get().await_ready()) { state->__suspend_point = 2; state->__resume = nullptr; // 标记为最终暂停点 coro_to_resume = state->__tmp4.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); tmp4_dtor.cancel(); goto resume_coro; } state->__tmp4.get().await_resume(); } // 如果执行流到达协程末尾,则销毁协程状态 delete state; return; ```   最终,还需要更新```__g_destroy```函数来处理这个新的暂停点: ```cpp void __g_destroy(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; case 2: goto suspend_point_2; default: std::unreachable(); } suspend_point_0: state->__tmp1.destroy(); goto destroy_state; suspend_point_1: state->__tmp3.destroy(); state->__tmp2.destroy(); goto destroy_state; suspend_point_2: state->__tmp4.destroy(); goto destroy_state; destroy_state: delete state; } ``` **实现对称转移和空操作协程**   协程规范中强烈建议编译器以尾调用的方式实现下一个协程的恢复,而不是递归地恢复下一个协程。这是因为如果协程在循环中相互恢复,递归地恢复下一个协程很容易导致无界的栈增长。而上面实现的```__g_resume()```函数体内调用下一个协程的```.resume()```,然后返回,因此__g_resume()帧使用的栈空间要到下一个协程暂停并返回后才会释放。   编译器能够通过将下一个协程的恢复实现为尾调用来做到这一点。通过这种方式,编译器生成的代码会先弹出当前栈帧(保留返回地址),然后执行jmp到下一个协程的恢复函数。由于在 C++ 中没有机制指定尾位置的函数调用应该是尾调用,我们需要从恢复函数返回,以便释放其栈空间,然后让调用者恢复下一个协程。由于下一个协程在暂停时可能还需要恢复另一个协程,而且这可能会无限进行下去,调用者需要在循环中恢复协程。这种循环通常称为 “蹦床循环”(trampoline loop),因为我们从一个协程返回到循环,然后从循环 “反弹” 到下一个协程。如果我们将恢复函数的签名修改为返回下一个协程的协程状态指针(而不是返回 void),那么```coroutine_handle::resume()```函数可以立即调用下一个协程的```__resume()```函数指针来恢复它。   因此修改```__coroutine_state```的```__resume_fn```签名: ```cpp struct __coroutine_state { using __resume_fn = __coroutine_state* (__coroutine_state*); using __destroy_fn = void (__coroutine_state*); __resume_fn* __resume; __destroy_fn* __destroy; }; ```   可以这样编写```coroutine_handle::resume()```函数: ```cpp void std::coroutine_handle::resume() const { __coroutine_state* s = state_; do { s = s->__resume(s); } while (/* 某种条件 */); } ```   现在的问题是如何添加终止条件。`std::noop_coroutine()` 是一个工厂函数,返回一个特殊的协程句柄,它具有空操作(no-op)的 `resume()` 和 `destroy()` 方法。如果一个协程暂停并从 `await_suspend()` 方法返回空操作协程句柄,这表明没有更多的协程需要恢复,恢复此协程的 `coroutine_handle::resume()` 调用应该返回到其调用者。因此,我们需要实现 `std::noop_coroutine()` 和 `coroutine_handle::resume()` 中的条件,以便当 `__coroutine_state` 指针指向空操作协程状态时,条件返回 `false`,循环退出。我们可以使用的一种策略是定义一个 `__coroutine_state` 的静态实例,指定为空操作协程状态。`std::noop_coroutine()` 函数可以返回一个指向此对象的协程句柄,我们可以将 `__coroutine_state` 指针与该对象的地址进行比较,以查看特定的协程句柄是否是空操作协程。 ```cpp struct __coroutine_state { using __resume_fn = __coroutine_state* (__coroutine_state*); using __destroy_fn = void (__coroutine_state*); __resume_fn* __resume; __destroy_fn* __destroy; static __coroutine_state* __noop_resume(__coroutine_state* state) noexcept { return state; } static void __noop_destroy(__coroutine_state*) noexcept {} static const __coroutine_state __noop_coroutine; }; inline const __coroutine_state __coroutine_state::__noop_coroutine{ &__coroutine_state::__noop_resume, &__coroutine_state::__noop_destroy }; namespace std { struct noop_coroutine_promise {}; using noop_coroutine_handle = coroutine_handle; noop_coroutine_handle noop_coroutine() noexcept; template<> class coroutine_handle { public: constexpr coroutine_handle(const coroutine_handle&) noexcept = default; constexpr coroutine_handle& operator=(const coroutine_handle&) noexcept = default; constexpr explicit operator bool() noexcept { return true; } constexpr friend bool operator==(coroutine_handle, coroutine_handle) noexcept { return true; } operator coroutine_handle() const noexcept { return coroutine_handle::from_address(address()); } noop_coroutine_promise& promise() const noexcept { static noop_coroutine_promise promise; return promise; } constexpr void resume() const noexcept {} constexpr void destroy() const noexcept {} constexpr bool done() const noexcept { return false; } constexpr void* address() const noexcept { return const_cast<__coroutine_state*>(&__coroutine_state::__noop_coroutine); } private: constexpr coroutine_handle() noexcept = default; friend noop_coroutine_handle noop_coroutine() noexcept { return {}; } }; } void std::coroutine_handle::resume() const { __coroutine_state* s = state_; do { s = s->__resume(s); } while (s != &__coroutine_state::__noop_coroutine); } __coroutine_state* __g_resume(__coroutine_state* s) { auto* state = static_cast<__g_state*>(s); try { switch (state->__suspend_point) { case 0: goto suspend_point_0; case 1: goto suspend_point_1; // <-- 添加新的跳转表条目 default: std::unreachable(); } suspend_point_0: { destructor_guard tmp1_dtor{state->__tmp1}; state->__tmp1.get().await_resume(); } // int fx = co_await f(x); { state->__s1.__tmp2.construct_from([&] { return f(state->x); }); destructor_guard tmp2_dtor{state->__s1.__tmp2}; state->__s1.__tmp3.construct_from([&] { return static_cast(state->__s1.__tmp2.get()).operator co_await(); }); destructor_guard tmp3_dtor{state->__s1.__tmp3}; if (!state->__s1.__tmp3.get().await_ready()) { state->__suspend_point = 1; auto h = state->__s1.__tmp3.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); // 协程暂停时不退出作用域 // 因此取消析构保护 tmp3_dtor.cancel(); tmp2_dtor.cancel(); return static_cast<__coroutine_state*>(h.address()); } // 不要在这里退出作用域 // 我们不能'goto'到进入具有非平凡析构函数的变量作用域的标签 // 因此我们必须在不调用析构函数的情况下退出析构保护的作用域,然后在`suspend_point_1`标签后重新创建它们 tmp3_dtor.cancel(); tmp2_dtor.cancel(); } suspend_point_1: int fx = [&]() -> decltype(auto) { destructor_guard tmp2_dtor{state->__s1.__tmp2}; destructor_guard tmp3_dtor{state->__s1.__tmp3}; return state->__s1.__tmp3.get().await_resume(); }(); // co_return fx * fx; state->__promise.return_value(fx * fx); goto final_suspend; } catch (...) { state->__promise.unhandled_exception(); goto final_suspend; } final_suspend: // co_await promise.final_suspend { state->__tmp4.construct_from([&]() noexcept { return state->__promise.final_suspend(); }); destructor_guard tmp4_dtor{state->__tmp4}; if (!state->__tmp4.get().await_ready()) { state->__suspend_point = 2; state->__resume = nullptr; // 标记为最终暂停点 auto h = state->__tmp4.get().await_suspend( std::coroutine_handle<__g_promise_t>::from_promise(state->__promise)); tmp4_dtor.cancel(); return static_cast<__coroutine_state*>(h.address()); } state->__tmp4.get().await_resume(); } // 如果执行流到达协程末尾,则销毁协程状态 delete state; return static_cast<__coroutine_state*>(std::noop_coroutine().address()); } ``` **协程state的内存占用优化**   协程状态类型```__g_state```实际上比需要的更大。然而,一些临时值的生命周期不重叠,因此理论上我们可以通过在一个对象的生命周期结束后重用其存储来节省协程状态的空间。由于`__tmp2`和`__tmp3`的生命周期重叠,我们必须将它们一起放在一个结构体中,因为它们都需要同时存在。然而,__tmp1和__tmp4的生命周期不重叠,因此它们可以一起放在匿名`union`中。 ```cpp struct __g_state : __coroutine_state_with_promise<__g_promise_t> { __g_state(int&& x); ~__g_state(); int __suspend_point = 0; int x; struct __scope1 { manual_lifetime __tmp2; manual_lifetime __tmp3; }; union { manual_lifetime __tmp1; __scope1 __s1; manual_lifetime __tmp4; }; }; ``` ## 4 协程使用可能存在问题 ### 4.1 避免内存分配   异步操作通常需要存储一些每个操作的状态,以跟踪操作的进展。这种状态通常需要在操作持续期间保持有效,并且只有在操作完成后才能释放。例如,调用异步 Win32 I/O 函数时,需要分配并传递一个指向 OVERLAPPED 结构的指针。调用者负责确保该指针在操作完成前保持有效。   在传统的基于回调的 API 中,这种状态通常需要在堆上分配,以确保它具有适当的生命周期。如果您执行多个操作,可能需要为每个操作分配和释放这种状态。如果性能是一个问题,可以使用自定义分配器,从池中分配这些状态对象。然而,当我们使用协程时,可以避免为操作状态进行堆分配,因为协程帧中的局部变量在协程挂起时会保持有效。通过将每个操作的状态放在 Awaiter 对象中,我们可以有效地“借用”协程帧的内存,用于存储每个操作的状态,直到 co_await 表达式完成。一旦操作完成,协程恢复,Awaiter 对象被销毁,从而释放协程帧中的内存供其他局部变量使用。   最终,协程帧可能仍然在堆上分配。然而,一旦分配,协程帧可以用于执行多个异步操作,而只需那一次堆分配。如果仔细考虑,协程帧实际上充当了一种高性能的区域内存分配器。编译器在编译时确定所需的总区域大小,然后能够以零开销的方式将这块内存分配给局部变量。 ### 4.2 理清协程和线程的区别   协程和线程都是用来实现异步编程的手段而已,都是在不同维度上所对应的产物。很多文章会将进程,线程,协程放在一起做描述区分,我个人理解其实不需要这么复杂,直接从执行层次上区分即可。对于用户态程序来讲,其执行代码从上到下的层次分别为协程/函数,系统线程,逻辑线程(或者叫硬件线程,这里不做区分)。任何用户态的代码最终要运行到CPU上都是要运行到硬件线程单元上的,只不过为了方便开发,将其线程模型通过操作系统包装成了系统线程(一般是m-n模型)。系统线程由操作系统调度,但是最终都会对应到有限的硬件线程上。协程类似,协程的异步是将异步调度权放在了用户态,可以认为是在线程上的更上一层包装,让用户态可以调度自己的任务。而且按照这个层次,以用户态的视角观察,越往上切换的开销约小,性能越优化,开发的灵活性越大。因此,C++ 标准提供的只是最基本的协程支持,如果要更合适的调度可以根据自己的开发场景开发对应的协程调度库来方便开发。   当然协程和线程关系又不是那么简单,虽然最终协程的代码运行都会落到线程上,但是协程的运行规则相比线程要复杂的多,需要相比线程更好的调度规划才能达到更好的性能。同时协程可以在一个线程上执行,也可以在多个线程上执行,这完全取决于开发者的意愿。所以在开发时,如果协程的切换存在线程切换也是要考虑多线程问题的。   另外,根据现有的开发状态来讲,C++ 协程和线程是不同生态位的东西,是相互弥补的。协程的编写和管理相对简单,尤其在处理非阻塞 I/O 时,可以让代码更清晰,避免回调地狱。协程在用户态中进行调度,具有更轻量级的特性,适合处理大量的异步操作,如 I/O 密集型任务。它们能有效减少上下文切换的开销,提高程序的响应性。线程能够利用操作系统的调度能力,更好地处理需要并行计算的复杂任务。线程则在多核处理器上更有效,适合 CPU 密集型任务。线程可以并行执行,充分利用多核 CPU 的计算能力。 ### 4.3 对称转移   对称转移是 C++20 协程中新增的关键功能,允许一个协程暂停时直接将执行权转移给另一个暂停的协程,且不产生额外栈空间消耗。其核心是通过await_suspend()返回```std::coroutine_handle```实现协程间的 “对称” 切换,配合编译器的尾调用优化(确保栈帧不累积),避免传统递归调用导致的栈溢出。同时,通过```std::noop_coroutine()```可在无其他协程可恢复时,将执行权返回给resume()的调用者。   在传统协程实现中,若协程通过co_await嵌套调用(如循环中同步完成的任务),会因每次resume()调用在栈上累积帧,导致类似递归的栈溢出。考虑下面的例子: ```cpp // 不支持对称转移的task类型实现(会导致栈溢出) class task { public: class promise_type { public: task get_return_object() noexcept { return task{std::coroutine_handle::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } void return_void() noexcept {} void unhandled_exception() noexcept { std::terminate(); } struct final_awaiter { bool await_ready() noexcept { return false; } // 直接resume导致栈帧累积 void await_suspend(std::coroutine_handle h) noexcept { h.promise().continuation.resume(); } void await_resume() noexcept {} }; final_awaiter final_suspend() noexcept { return {}; } std::coroutine_handle<> continuation; }; task(task&& t) noexcept : coro_(std::exchange(t.coro_, {})) {} task(const task&) = delete; task& operator=(const task&) = delete; ~task() { if (coro_) coro_.destroy(); } class awaiter { public: bool await_ready() noexcept { return false; } // 直接resume被等待协程,栈帧叠加 void await_suspend(std::coroutine_handle<> continuation) noexcept { coro_.promise().continuation = continuation; coro_.resume(); // 每次调用都会新增栈帧 } void await_resume() noexcept {} private: friend task; explicit awaiter(std::coroutine_handle h) noexcept : coro_(h) {} std::coroutine_handle coro_; }; awaiter operator co_await() && noexcept { return awaiter{coro_}; } // 新增:启动协程执行(关键修改,确保协程实际运行) void start() noexcept { if (coro_) coro_.resume(); } private: explicit task(std::coroutine_handle h) noexcept : coro_(h) {} std::coroutine_handle coro_; }; // 同步完成的协程 task completes_synchronously() { co_return; // 立即完成,触发final_suspend } // 循环等待同步任务(栈溢出的根源) task loop_synchronously(int count) { for (int i = 0; i < count; ++i) { co_await completes_synchronously(); // 每次循环都会嵌套resume } } // 启动器协程:用于触发loop_synchronously执行 task start_loop(int count) { co_await loop_synchronously(count); // 启动循环协程 } int testSym() { spdlog::set_level(spdlog::level::info); spdlog::info("Starting test (will crash due to stack overflow)"); // 关键修改:创建启动器并执行,触发完整调用链 auto t = start_loop(1'000'000); t.start(); // 启动根协程,开始执行整个调用链 spdlog::info("This line will never be reached"); return 0; } ```   上述代码中,```loop_synchronously```协程在循环中反复`co_await completes_synchronously`,形成了一种 “隐性递归” 的执行模式。我们通过拆解单次循环的执行步骤,分析栈帧的变化: 1. 启动根协程,初始化栈帧: - `testSym`函数中,`start_loop(1'000'000)`创建一个`task`对象,随后调用`t.start()`触发根协程`start_loop`执行。 - `start_loop`的`resume()`被调用,栈上创建第一个栈帧:`start_loop$resume`(协程体执行部分)。 2. `start_loop`等待`loop_synchronously`,栈帧 + 1 - `start_loop`执行`co_await loop_synchronously(1'000'000)`,触发`loop_synchronously`的创建: - `loop_synchronously`的协程帧在堆上分配,初始挂起后返回`task`对象。 - `start_loop`暂停,调用`loop_synchronously`的`await_suspend`方法,该方法通过`coro_.resume()`恢复`loop_synchronously`执行。此时栈上新增第二个栈帧:`loop_synchronously$resume`。 3. `loop_synchronously`第一次循环,等待`completes_synchronously`,栈帧 + 2 - `loop_synchronously`进入循环,执行`co_await completes_synchronously()`: - `completes_synchronously`创建并挂起,返回`task`对象。 `loop_synchronously`暂停,调用`completes_synchronously`的`await_suspend`方法,该方法通过`coro_.resume()`恢复`completes_synchronously`执行。 - 栈上新增第三个栈帧:`completes_synchronously$resume`。`completes_synchronously`执行到`co_return`,触发`final_suspend`,其`final_awaiter`的`await_suspend`调用`loop_synchronously`的`resume()`(恢复循环)。 - 栈上新增第四个栈帧:`loop_synchronously$resume`(第二次进入循环体)。 4. 循环累积,栈帧无界增长 - 每次循环迭代中,`loop_synchronously`和`completes_synchronously`会相互通过`resume()`恢复对方执行: - `completes_synchronously`完成后,`final_awaiter`调用`loop_synchronously.resume()`,栈上新增`loop_synchronously$resume`帧。 - `loop_synchronously`再次`co_await`时,调用`completes_synchronously.resume()`,栈上新增`completes_synchronously$resume`帧。   每轮循环会新增2 个栈帧,且这些帧在循环结束前不会被释放(因为`resume()`的调用者仍在栈上等待返回),当循环调用比较多时导致栈帧积累过多导致栈溢出。解决该问题的核心解决方案是避免协程切换时的栈帧累积。以下两个方案: 1. 通过```await_suspend()```返回```std::coroutine_handle```实现协程间的 “对称转移”,配合编译器的尾调用优化,在切换协程时释放当前栈帧,避免累积。 ```cpp class task { public: class promise_type { public: // ...(其他代码同前) struct final_awaiter { bool await_ready() noexcept { return false; } // 对称转移:返回延续协程的句柄(尾调用优化) std::coroutine_handle<> await_suspend(std::coroutine_handle h) noexcept { return h.promise().continuation; // 直接返回句柄,不调用resume() } void await_resume() noexcept {} }; // ...(其他代码同前) }; class awaiter { public: // ...(其他代码同前) // 对称转移:返回被等待协程的句柄(尾调用优化) std::coroutine_handle<> await_suspend(std::coroutine_handle<> continuation) noexcept { coro_.promise().continuation = continuation; return coro_; // 返回句柄,由编译器处理尾调用跳转 } }; // ...(其他代码同前) }; ``` 2. 使用原子变量检测同步完成。通过```std::atomic```标记协程是否同步完成,若已完成则直接恢复当前协程。 ```cpp class task { public: class promise_type { public: // ...(其他代码同前) std::atomic is_completed{false}; // 标记是否已完成 std::coroutine_handle<> continuation; }; class awaiter { public: // ...(其他代码同前) // 返回bool:false表示直接恢复当前协程 bool await_suspend(std::coroutine_handle<> continuation) noexcept { coro_.promise().continuation = continuation; coro_.resume(); // 执行被等待协程 // 若已同步完成,返回false直接恢复当前协程(无栈帧累积) return !coro_.promise().is_completed.exchange(true); } }; // 修改final_awaiter:同步完成时不调用resume() struct final_awaiter { bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle h) noexcept { h.promise().is_completed = true; // 标记完成 // 仅在异步完成时才恢复(同步完成时由await_suspend直接恢复) if (!h.promise().continuation.done()) { h.promise().continuation.resume(); } } }; }; ``` ## 5 参考文献 - [Cppreference coroutine](https://en.cppreference.com/w/cpp/language/coroutines.html) - [Coroutine Theory](https://lewissbaker.github.io/2017/09/25/coroutine-theory) - [C++ Coroutines: Understanding operator co_await](https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await) - [C++ Coroutines: Understanding the promise type](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type) - [C++ Coroutines: Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer) - [C++ Coroutines: Understanding the Compiler Transform](https://lewissbaker.github.io/2022/08/27/understanding-the-compiler-transform)