【C++】从代码到原理:逐层解构C++20协程的promise_type、Awaitable与co_yield核心机制

文章目录

  • C++协程的代码逻辑
  • [详细介绍Awaitable 类型](#详细介绍Awaitable 类型)
      • [一、Awaitable 类型的核心定义](#一、Awaitable 类型的核心定义)
      • [二、Awaitable 类型的核心规范(3个必须实现的接口)](#二、Awaitable 类型的核心规范(3个必须实现的接口))
      • [三、Awaitable 类型的两种实现方式](#三、Awaitable 类型的两种实现方式)
      • [四、Awaitable 类型的核心分类(按用途)](#四、Awaitable 类型的核心分类(按用途))
        • [1. 基础暂停型(返回void)](#1. 基础暂停型(返回void))
        • [2. 自动恢复型(返回coroutine_handle)](#2. 自动恢复型(返回coroutine_handle))
        • [3. 取消暂停型(返回bool)](#3. 取消暂停型(返回bool))
      • [五、Awaitable 类型的实际应用例子(异步任务)](#五、Awaitable 类型的实际应用例子(异步任务))
      • [总结(Awaitable 类型核心要点)](#总结(Awaitable 类型核心要点))
  • Awaitable类型的设计初衷
      • [一、Awaitable 类型的设计初衷:解决"暂停-等待-恢复"的通用化问题](#一、Awaitable 类型的设计初衷:解决“暂停-等待-恢复”的通用化问题)
        • [1. 为什么需要"通用化"?](#1. 为什么需要“通用化”?)
        • [2. 3个核心接口的设计逻辑(为什么是这3个函数?)](#2. 3个核心接口的设计逻辑(为什么是这3个函数?))
      • 二、理解std::suspend_always/never:极简的Awaitable"基础款"
        • [1. 先看标准库的核心实现(简化版)](#1. 先看标准库的核心实现(简化版))
        • [2. 为什么要设计这两个类型?](#2. 为什么要设计这两个类型?)
        • [3. 结合之前的Generator代码理解](#3. 结合之前的Generator代码理解)
      • 三、理解co_yield:Awaitable的"语法糖"
        • [1. co_yield的编译展开(底层本质)](#1. co_yield的编译展开(底层本质))
        • [2. 为什么要设计co_yield?](#2. 为什么要设计co_yield?)
        • [3. 结合代码理解co_yield的执行流程](#3. 结合代码理解co_yield的执行流程)
      • 总结(核心设计逻辑)

C++协程的代码逻辑

cpp 复制代码
#include <coroutine>
#include <iostream>
#include <memory>
#include <optional>

// -------------------------- 1. 协程返回类型(可等待对象) --------------------------
// 定义协程的返回类型,需包含coroutine_handle和promise_type的绑定
struct Generator {
    // 核心:协程句柄,用于控制协程的暂停/恢复/销毁(指向堆上的协程状态对象)
    using handle_type = std::coroutine_handle<>;

    // ---------------------- 2. Promise类型(协程状态载体) ----------------------
    // 编译器要求:返回类型必须包含promise_type,用于存储协程状态、传递数据
    struct promise_type {
        // 存储协程产生的值(跨暂停点保留,序列化到堆)
        std::optional<int> value;
        // 存储协程是否结束的标记
        bool done = false;

        // 【编译器自动调用】协程创建时第一个调用的函数:返回协程的返回对象(Generator)
        Generator get_return_object() {
            std::cout << "[Promise] 初始化协程返回对象\n";
            // 将当前协程的handle绑定到Generator
            return Generator{handle_type::from_promise(*this)};
        }

        // 【编译器自动调用】协程启动时的暂停策略:std::suspend_always=立即暂停
        std::suspend_always initial_suspend() {
            std::cout << "[Promise] 协程启动,立即暂停(等待resume)\n";
            return {}; // 让协程在入口处暂停,需手动resume才开始执行
        }

        // 【编译器自动调用】协程结束时的暂停策略:std::suspend_always=暂停(避免立即销毁)
        std::suspend_always final_suspend() noexcept {
            std::cout << "[Promise] 协程执行结束,标记完成\n";
            done = true;
            return {};
        }

        // 【编译器自动调用】处理co_yield:接收协程产生的值,暂停协程
        std::suspend_always yield_value(int val) {
            std::cout << "[Promise] 捕获co_yield值:" << val << ",暂停协程\n";
            // 将值存储到promise(堆上),跨暂停点保留
            value = val;
            return {}; // 暂停协程,等待下一次resume
        }

        // 【编译器自动调用】处理协程返回(co_return)
        void return_void() {
            std::cout << "[Promise] 协程无返回值,完成执行\n";
        }

        // 【编译器自动调用】处理协程内未捕获的异常
        void unhandled_exception() {
            std::terminate(); // 简单处理:终止程序
        }
    };

    // ---------------------- Generator类的核心方法 ----------------------
    // 构造函数:绑定协程句柄
    explicit Generator(handle_type h) : coro_handle_(h) {}

    // 析构函数:销毁协程句柄,释放堆上的状态对象
    ~Generator() {
        if (coro_handle_) {
            std::cout << "[Generator] 销毁协程句柄,释放堆状态对象\n";
            coro_handle_.destroy();
        }
    }

    // 禁用拷贝(避免重复销毁句柄),启用移动
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;
    Generator(Generator&& other) noexcept : coro_handle_(other.coro_handle_) {
        other.coro_handle_ = nullptr;
    }
    Generator& operator=(Generator&& other) noexcept {
        if (this != &other) {
            if (coro_handle_) coro_handle_.destroy();
            coro_handle_ = other.coro_handle_;
            other.coro_handle_ = nullptr;
        }
        return *this;
    }

    // 恢复协程执行,返回是否还有值
    bool resume() {
        if (!coro_handle_ || coro_handle_.done()) return false;
        std::cout << "[Generator] 恢复协程执行\n";
        coro_handle_.resume(); // 核心:恢复协程,执行到下一个暂停点/结束
        return !coro_handle_.done();
    }

    // 获取协程产生的值(从promise的堆状态中读取)
    int value() const {
        // promise():获取句柄绑定的promise对象(堆上)
        return coro_handle_.promise().value.value();
    }

private:
    handle_type coro_handle_; // 协程句柄,指向堆上的协程状态对象
};

// -------------------------- 3. 协程函数(业务逻辑) --------------------------
// 无栈协程函数:生成1~3的整数序列,每次co_yield暂停
Generator generate_numbers() {
    std::cout << "[协程] 开始执行,准备生成数字\n";
    co_yield 1; // 暂停点1:将1存入promise,暂停
    co_yield 2; // 暂停点2:将2存入promise,暂停
    co_yield 3; // 暂停点3:将3存入promise,暂停
    std::cout << "[协程] 数字生成完成,即将结束\n";
}

// -------------------------- 4. 主函数(协程调用者) --------------------------
int main() {
    std::cout << "===== 启动协程 =====\n";
    // 创建协程:编译器自动生成堆上的状态对象,协程在initial_suspend处暂停
    Generator gen = generate_numbers();

    // 循环恢复协程,获取值
    while (gen.resume()) {
        std::cout << "[主线程] 获取协程值:" << gen.value() << "\n\n";
    }

    std::cout << "\n===== 协程执行完毕 =====\n";
    return 0;
}

协程底层原理的角度,理解这段C++20无栈协程代码为什么要这么设计和编写,核心是搞清楚协程状态管理、暂停/恢复机制、数据传递的底层逻辑。

一、先理解C++20协程的核心底层模型

C++20协程本质是无栈协程(Stackless Coroutine),其底层核心原理可以概括为:

  1. 协程启动时,编译器会将协程的状态(局部变量、暂停点、指令指针) 打包成一个堆上的状态对象(而非栈帧);
  2. 协程的暂停(co_yield/co_await)本质是「保存当前指令指针+释放调用栈」,恢复(resume)是「恢复指令指针+重新占用调用栈」;
  3. 协程的返回类型(如Generator)必须通过promise_type与堆上的状态对象绑定,promise_type是协程状态的"载体",负责数据传递和生命周期管理。

下面结合代码逐模块拆解"为什么要这么写":


二、逐模块解析底层逻辑

1. 协程返回类型Generator:为什么必须包含promise_type

底层原理

C++编译器对协程有强制要求:协程的返回类型必须嵌套定义promise_type,因为:

  • 协程启动时,编译器会先创建promise_type对象(作为协程状态的核心部分,分配在堆上);
  • 所有跨暂停点的数据(如co_yield的值)、协程的暂停/恢复策略,都由promise_type管理;
  • Generator作为"协程控制器",需要通过std::coroutine_handle绑定到堆上的promise_type,从而实现对协程的控制(恢复、销毁、读取数据)。

代码设计的底层原因

cpp 复制代码
struct Generator {
    using handle_type = std::coroutine_handle<>; // 绑定到promise_type的句柄
    struct promise_type { ... }; // 编译器强制要求的协程状态载体
    handle_type coro_handle_;   // 指向堆上协程状态的句柄
};
  • coroutine_handle:是协程的"底层句柄",本质是一个指针,指向堆上的协程状态对象(包含promise_type、指令指针、局部变量等);
  • 禁用拷贝、启用移动:因为coroutine_handle指向唯一的堆状态对象,拷贝会导致重复销毁(双重释放),移动是唯一安全的传递方式。
2. promise_type:为什么要实现这些成员函数?

promise_type的所有成员函数都是编译器自动调用的,对应协程生命周期的关键节点,缺一不可:

成员函数 底层调用时机 为什么要这么实现?
get_return_object() 协程创建时第一个调用 返回Generator对象,并将当前协程的handle绑定到Generator,让调用者能控制协程;
initial_suspend() 协程启动后(执行第一行代码前) 返回std::suspend_always:让协程"启动即暂停",等待调用者手动resume(避免协程一次性执行完);
final_suspend() 协程执行结束时 返回std::suspend_always:避免协程结束后立即销毁堆状态对象,等待Generator析构时手动销毁;
yield_value(int val) 遇到co_yield val 接收co_yield的值并存储到promise_type(堆上),然后暂停协程(跨暂停点保留数据);
return_void() 协程无co_return时自动调用 标记协程无返回值,完成执行;
unhandled_exception() 协程内抛出未捕获异常时 处理异常(这里简单终止程序,避免内存泄漏);

核心底层细节

  • std::suspend_always/std::suspend_never:是C++20提供的"暂停策略",本质是包含await_ready()/await_suspend()/await_resume()的空结构体;
  • yield_value返回suspend_always:让协程在co_yield处暂停,此时堆上的promise_type保存了val,调用者可以通过Generator读取;
  • promise_typevaluestd::optional:因为协程启动时还未产生值,optional可以安全处理"无值"状态。
3. Generator的核心方法:resume()value()

resume()的底层逻辑

cpp 复制代码
bool resume() {
    if (!coro_handle_ || coro_handle_.done()) return false;
    coro_handle_.resume(); // 核心:恢复协程执行
    return !coro_handle_.done();
}
  • coro_handle_.resume():底层是"恢复协程的指令指针",让协程从上次暂停的位置继续执行,直到下一个co_yield或协程结束;
  • coro_handle_.done():判断协程是否执行完毕(指令指针到达协程末尾),避免重复恢复。

value()的底层逻辑

cpp 复制代码
int value() const {
    return coro_handle_.promise().value.value();
}
  • coro_handle_.promise():底层是通过句柄指针,直接访问堆上的promise_type对象;
  • 读取promise_typevalue:因为promise_type在堆上,跨暂停点不会销毁,所以能安全读取co_yield的值。
4. 析构函数:为什么要调用coro_handle_.destroy()
cpp 复制代码
~Generator() {
    if (coro_handle_) {
        coro_handle_.destroy();
        coro_handle_ = nullptr;
    }
}

底层原因

  • 协程的状态对象(包含promise_type、指令指针、局部变量)分配在堆上,编译器不会自动释放;
  • coro_handle_.destroy():是唯一释放堆上协程状态的方式,若不调用会导致内存泄漏
  • 析构时检查coro_handle_是否为空:避免空指针调用destroy()
5. 协程函数generate_numbers():为什么用co_yield
cpp 复制代码
Generator generate_numbers() {
    co_yield 1;
    co_yield 2;
    co_yield 3;
}

底层原理

  • co_yield val:是C++20的语法糖,等价于co_await promise.yield_value(val)
  • 每次co_yield:编译器会调用promise_type::yield_value(val),将值存入堆上的promise_type,然后暂停协程,释放调用栈;
  • 协程的局部变量(若有):会被编译器打包到堆上的状态对象中,跨暂停点保留。

三、协程执行的完整底层流程(对应main函数)

cpp 复制代码
Generator gen = generate_numbers(); // 协程创建
while (gen.resume()) { ... }        // 循环恢复协程
  1. 创建协程
    • 编译器分配堆上的协程状态对象(包含promise_type、指令指针、局部变量);
    • 调用promise.get_return_object(),返回绑定了句柄的Generator
    • 调用promise.initial_suspend(),协程在入口处暂停(未执行任何业务逻辑)。
  2. 第一次gen.resume()
    • 恢复协程,执行到co_yield 1
    • 调用promise.yield_value(1),将1存入堆上的promise_type,协程暂停;
    • 主线程读取gen.value(),获取堆上的1。
  3. 第二次gen.resume()
    • 恢复协程,从co_yield 1的位置继续执行到co_yield 2
    • 重复上述流程,存入2并暂停。
  4. 第三次gen.resume()
    • 恢复协程,执行到co_yield 3,存入3并暂停。
  5. 第四次gen.resume()
    • 恢复协程,执行完剩余代码("数字生成完成"),调用promise.return_void()
    • 调用promise.final_suspend(),协程标记为done
    • gen.resume()返回false,循环结束。
  6. gen析构
    • 调用coro_handle_.destroy(),释放堆上的协程状态对象。

总结(核心底层要点)

  1. 堆上状态管理 :C++20协程的核心是将状态(数据+指令指针)分配在堆上,而非栈上,因此必须通过promise_type存储跨暂停点的数据,通过coroutine_handle控制状态;
  2. 编译器强制约定promise_type的成员函数(get_return_object/initial_suspend等)是编译器的"强制接口",必须实现,否则无法编译,这些函数对应协程生命周期的关键节点;
  3. 手动生命周期控制 :协程的堆状态不会自动释放,必须通过coroutine_handle_.destroy()手动销毁(通常在返回类型的析构函数中),否则会内存泄漏;
  4. 暂停/恢复的本质co_yield/resume是"保存指令指针+释放栈"和"恢复指令指针+占用栈"的过程,堆上的promise_type是数据传递的桥梁。

这段代码的每一个细节,都是为了适配C++20协程的底层模型------通过promise_type管理堆状态,通过coroutine_handle控制协程,通过暂停/恢复实现"分段执行"。

详细介绍Awaitable 类型

深入理解C++20协程中的Awaitable(可等待对象) 类型,这是协程暂停/恢复机制的核心,从定义、核心接口、底层原理、分类和实际例子几个维度,把这个概念讲透。

一、Awaitable 类型的核心定义

Awaitable(可等待对象) 是满足C++20协程标准的一种类型,本质是:

任何能被co_await运算符作用的对象,都称为"Awaitable类型"。

简单来说,co_await expr能否编译通过,完全取决于expr的类型是否是Awaitable------这是协程实现"暂停等待某个条件,条件满足后恢复"的核心载体(比如co_yield val本质就是co_await promise.yield_value(val),而yield_value的返回值就是Awaitable类型)。

底层逻辑:co_await的编译展开

co_await awaitable时,编译器会自动将其展开为以下逻辑(伪代码):

cpp 复制代码
// 1. 获取awaiter(等待器):awaitable的核心执行体
auto&& awaiter = get_awaitable(awaitable); 
// 2. 检查是否需要暂停:若ready=true,直接返回结果,不暂停
if (!awaiter.await_ready()) {
    // 3. 暂停协程:保存协程状态,将句柄传给awaiter,释放调用栈
    awaiter.await_suspend(coroutine_handle); 
}
// 4. 恢复后执行:获取等待结果(无论是否暂停,最终都会执行)
auto result = awaiter.await_resume(); 

这个展开逻辑也决定了:Awaitable类型的核心是提供一个符合规范的awaiter(等待器)


二、Awaitable 类型的核心规范(3个必须实现的接口)

Awaitable类型的本质是"能生成符合规范的awaiter",而awaiter必须实现以下3个成员函数(缺一不可,编译器强制要求):

成员函数 返回值类型 核心作用 调用时机
await_ready() bool 判断是否需要暂停协程: - true:无需暂停,直接执行await_resume() - false:需要暂停,执行await_suspend() co_await执行的第一步
await_suspend(handle) void/bool/std::coroutine_handle<> 协程暂停时的逻辑: - 保存协程句柄、注册回调(比如异步任务完成后恢复协程) - 返回值控制是否真正暂停 await_ready()返回false
await_resume() 任意类型(或void) 协程恢复后的逻辑: - 返回等待的结果(比如异步任务的返回值、co_yield的值) 协程恢复后(或未暂停时)
关键补充:
  • await_suspend的参数:必须是std::coroutine_handle<Promise>(协程句柄),用于操作当前暂停的协程;
  • 接口的访问权限:这3个函数可以是public,也可以是private(编译器能绕过访问控制调用);
  • 返回值灵活度:await_resume的返回值会成为co_await awaitable整个表达式的结果。

三、Awaitable 类型的两种实现方式

C++20允许两种方式定义Awaitable类型,本质都是为了生成符合规范的awaiter:

方式1:自身就是awaiter(最常用)

直接在类型中实现await_ready()await_suspend()await_resume(),此时该类型既是Awaitable,也是awaiter。

C++标准库提供的std::suspend_always/std::suspend_never就是典型例子:

cpp 复制代码
// 标准库实现(简化版)
struct suspend_always {
    // 总是需要暂停
    constexpr bool await_ready() const noexcept { return false; }
    // 暂停时不做额外操作,直接返回void(表示真正暂停)
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    // 恢复后无返回值
    constexpr void await_resume() const noexcept {}
};

struct suspend_never {
    // 从不暂停
    constexpr bool await_ready() const noexcept { return true; }
    // 不会被调用(因为await_ready返回true)
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    // 无返回值
    constexpr void await_resume() const noexcept {}
};

之前代码中的initial_suspend()/final_suspend()/yield_value()都返回suspend_always,就是利用这种方式让协程暂停。

方式2:通过operator co_await生成awaiter

如果类型本身不想实现3个核心接口,可以重载operator co_await,让这个运算符返回一个符合规范的awaiter。

这种方式适合"给已有类型(如std::future)增加Awaitable能力",示例:

cpp 复制代码
// 假设有一个简单的Future类型
template <typename T>
struct Future {
    T value;
    bool ready = false;

    // 重载operator co_await,返回awaiter
    auto operator co_await() {
        // 定义匿名awaiter(实现3个核心接口)
        struct Awaiter {
            Future& future; // 绑定外部的Future
            std::coroutine_handle<> handle; // 保存协程句柄

            // 检查Future是否已就绪
            bool await_ready() const noexcept {
                return future.ready;
            }

            // 暂停协程,保存句柄(比如注册回调:Future就绪后恢复协程)
            void await_suspend(std::coroutine_handle<> h) noexcept {
                handle = h;
                // 模拟:异步任务完成后,调用handle.resume()恢复协程
            }

            // 恢复后返回Future的值
            T await_resume() noexcept {
                return future.value;
            }
        };

        return Awaiter{*this}; // 返回awaiter
    }
};

// 此时Future就是Awaitable类型,可以直接co_await
Generator use_future() {
    Future<int> f;
    int val = co_await f; // 合法,因为Future重载了operator co_await
    co_yield val;
}

四、Awaitable 类型的核心分类(按用途)

根据await_suspend的返回值和使用场景,Awaitable类型主要分为3类,覆盖协程的核心用法:

1. 基础暂停型(返回void)
  • 特点:await_suspend返回void,表示"协程一定会暂停,且暂停后需要外部手动恢复";
  • 典型例子:std::suspend_always、之前代码中yield_value返回的对象;
  • 适用场景:手动控制协程暂停/恢复(如Generator生成器)。
2. 自动恢复型(返回coroutine_handle)
  • 特点:await_suspend返回另一个协程句柄,编译器会自动恢复该句柄指向的协程;

  • 适用场景:协程间切换(比如A协程暂停后,自动恢复B协程执行);

  • 示例:

    cpp 复制代码
    struct SwitchCoroutine {
        std::coroutine_handle<> target_handle;
    
        bool await_ready() const noexcept { return false; }
        // 返回目标协程句柄,编译器会自动resume这个句柄
        std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept {
            return target_handle;
        }
        void await_resume() const noexcept {}
    };
3. 取消暂停型(返回bool)
  • 特点:await_suspend返回bool

    • true:正常暂停(需要外部恢复);
    • false:取消暂停,立即执行await_resume()
  • 适用场景:动态决定是否暂停(比如根据运行时条件);

  • 示例:

    cpp 复制代码
    struct ConditionalSuspend {
        bool should_suspend;
    
        bool await_ready() const noexcept { return false; }
        bool await_suspend(std::coroutine_handle<>) noexcept {
            return should_suspend; // true=暂停,false=取消暂停
        }
        void await_resume() const noexcept {}
    };

五、Awaitable 类型的实际应用例子(异步任务)

下面用一个完整的例子,展示Awaitable类型如何实现"异步任务完成后自动恢复协程",这也是Awaitable最核心的实际用途:

cpp 复制代码
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>

// 1. 定义异步任务的返回类型(协程返回类型)
struct AsyncTask {
    struct promise_type {
        int result; // 存储异步任务的结果
        std::coroutine_handle<> handle; // 保存协程句柄

        AsyncTask get_return_object() {
            return AsyncTask{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(int val) { result = val; }
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> coro_handle;
};

// 2. 定义Awaitable类型:异步等待1秒后返回结果
struct SleepAwaitable {
    int sleep_ms;

    // 实现awaiter接口
    bool await_ready() const noexcept {
        // 若睡眠时间为0,无需暂停
        return sleep_ms == 0;
    }

    void await_suspend(std::coroutine_handle<> handle) noexcept {
        // 启动新线程:睡眠指定时间后,恢复协程
        std::thread([handle, ms = sleep_ms]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(ms));
            std::cout << "[异步线程] 睡眠完成,恢复协程\n";
            handle.resume(); // 核心:自动恢复暂停的协程
        }).detach();
    }

    int await_resume() const noexcept {
        // 恢复后返回结果
        return sleep_ms;
    }
};

// 3. 协程函数:使用Awaitable类型
AsyncTask async_operation() {
    std::cout << "[协程] 开始执行,准备睡眠1秒\n";
    // co_await SleepAwaitable:暂停协程,异步睡眠后自动恢复
    int ms = co_await SleepAwaitable{1000};
    std::cout << "[协程] 恢复执行,睡眠了" << ms << "ms\n";
    co_return ms; // 返回结果
}

int main() {
    AsyncTask task = async_operation();
    // 等待异步任务完成(简化版,实际需更安全的同步)
    std::this_thread::sleep_for(std::chrono::milliseconds(1500));
    std::cout << "[主线程] 异步任务结果:" << task.coro_handle.promise().result << std::endl;
    task.coro_handle.destroy();
    return 0;
}

执行流程解析

  1. 调用async_operation(),协程启动,执行到co_await SleepAwaitable{1000}
  2. 调用SleepAwaitable::await_ready(),返回false(需要暂停);
  3. 调用await_suspend():启动新线程睡眠1秒,同时协程暂停,释放主线程;
  4. 1秒后,异步线程调用handle.resume(),恢复协程;
  5. 协程执行await_resume(),获取返回值1000,最终执行co_return

总结(Awaitable 类型核心要点)

  1. 核心本质 :Awaitable是能被co_await作用的类型,其核心是提供符合规范的awaiter(实现await_ready/await_suspend/await_resume);
  2. 编译逻辑co_await会自动展开为"检查是否暂停→暂停处理→恢复获取结果"三步,完全依赖awaiter的3个接口;
  3. 实现方式 :要么自身实现3个接口(如suspend_always),要么重载operator co_await返回awaiter;
  4. 核心价值:是协程"暂停等待"的载体,既能实现手动控制(如Generator),也能实现自动恢复(如异步任务)。

理解Awaitable类型,就理解了C++20协程暂停/恢复的底层逻辑------所有co_await/co_yield的行为,最终都由Awaitable类型的awaiter接口决定。

Awaitable类型的设计初衷

C++20协程中Awaitable类型的设计初衷std::suspend_always/never的作用,以及co_yield的底层本质------核心是搞清楚这些设计是如何服务于"协程暂停/恢复"这一核心需求的。

一、Awaitable 类型的设计初衷:解决"暂停-等待-恢复"的通用化问题

C++20设计Awaitable类型的核心目标,是为协程的暂停/恢复提供一套通用、可扩展的接口,而非硬编码死暂停逻辑。

1. 为什么需要"通用化"?

协程的暂停场景千差万别:

  • 有的场景需要"启动即暂停"(如Generator生成器,等调用者手动恢复);
  • 有的场景需要"等待异步任务完成后自动恢复"(如网络请求、文件IO);
  • 有的场景需要"条件满足时才暂停"(如根据运行时参数决定是否暂停)。

如果编译器直接硬编码暂停逻辑,就无法适配这些差异化场景。因此,C++20将"是否暂停、暂停时做什么、恢复后返回什么"的逻辑,剥离到用户自定义的Awaitable类型中,让编译器只负责执行通用流程,具体逻辑由用户控制。

2. 3个核心接口的设计逻辑(为什么是这3个函数?)

Awaitable的await_ready/await_suspend/await_resume是对"暂停-恢复"流程的最小化抽象
渲染错误: Mermaid 渲染失败: Parse error on line 2: ... --> B[调用await_ready()] B -->|返回true -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

  • await_ready()前置判断,避免无意义的暂停(比如异步任务已经完成,就没必要暂停协程);
  • await_suspend()暂停时的核心逻辑,负责保存协程句柄、注册恢复回调(这是"等待"的核心);
  • await_resume()恢复后的收尾 ,返回等待结果(比如异步任务的返回值、co_yield的值)。

这套设计既满足了"必须暂停"(如Generator)、"永不暂停"(如同步任务)、"条件暂停"(如动态判断)等基础场景,也支持"异步自动恢复"(如网络IO)等复杂场景------通用性拉满,且无冗余


二、理解std::suspend_always/never:极简的Awaitable"基础款"

std::suspend_alwaysstd::suspend_never是C++标准库提供的最简化Awaitable类型,专门用于处理"无条件暂停"和"无条件不暂停"这两种最基础的场景。

1. 先看标准库的核心实现(简化版)
cpp 复制代码
// 无条件暂停:协程一定会暂停,且暂停后无额外逻辑
struct suspend_always {
    // 必须暂停
    constexpr bool await_ready() const noexcept { return false; }
    // 暂停时不做任何操作(只需保存协程状态,等外部手动恢复)
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    // 恢复后无返回值
    constexpr void await_resume() const noexcept {}
};

// 无条件不暂停:协程永不暂停,直接执行后续逻辑
struct suspend_never {
    // 无需暂停
    constexpr bool await_ready() const noexcept { return true; }
    // 永远不会被调用(因为await_ready返回true)
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    // 无返回值
    constexpr void await_resume() const noexcept {}
};
2. 为什么要设计这两个类型?

它们是"通用接口的极简实现",解决了协程生命周期中两个核心的默认需求:

场景 使用哪个? 核心作用
协程启动时暂停(如Generator) suspend_always 让协程"创建后不立即执行",等待调用者手动resume()(避免协程一次性跑完);
协程启动时立即执行(如异步任务) suspend_never 让协程"创建后直接执行",直到遇到第一个co_await/co_yield才暂停;
协程结束时暂停 suspend_always 避免协程结束后立即销毁堆状态对象,等待用户手动释放(防止内存泄漏);
3. 结合之前的Generator代码理解
cpp 复制代码
// 协程启动时立即暂停,等调用者resume()
std::suspend_always initial_suspend() { return {}; }

// 协程结束时暂停,等析构函数destroy()
std::suspend_always final_suspend() noexcept { return {}; }

// co_yield时暂停,保存值后等下一次resume()
std::suspend_always yield_value(int val) { return {}; }

这里全部返回suspend_always,本质是:

  • 协程的所有暂停点(启动、yield、结束)都采用"无条件暂停+手动恢复"的逻辑;
  • 这是Generator生成器的核心需求:调用者通过resume()控制协程的执行节奏,逐次获取值。

三、理解co_yield:Awaitable的"语法糖"

co_yield是C++20为"生成器场景"提供的语法糖 ,其底层完全依赖Awaitable类型实现------本质是co_await promise.yield_value(val)的简写。

1. co_yield的编译展开(底层本质)

当写:

cpp 复制代码
co_yield 1;

编译器会自动将其展开为:

cpp 复制代码
co_await promise.yield_value(1);

整个流程拆解:

  1. 调用协程对应的promise_type::yield_value(1),传入要生成的值;
  2. yield_value返回一个Awaitable类型(比如代码中的suspend_always);
  3. 执行co_await逻辑:调用该Awaitable的await_ready()(返回false)→ 调用await_suspend()(暂停协程)→ 等待恢复后调用await_resume()
  4. 在此过程中,yield_value会将值存入promise_type(堆上),供调用者读取。
2. 为什么要设计co_yield?
  • 简化生成器代码 :生成器是协程最基础的应用场景(逐次生成值),如果每次都写co_await promise.yield_value(val),代码会非常冗余;
  • 语义化co_yield直接表达"产出一个值并暂停"的语义,比co_await更贴合生成器的业务逻辑;
  • 与Awaitable解耦co_yield不关心yield_value返回的具体Awaitable类型,只需符合规范------可以返回suspend_always(手动恢复),也可以返回自定义Awaitable(比如"自动恢复"的生成器)。
3. 结合代码理解co_yield的执行流程
cpp 复制代码
// 协程函数中的co_yield 1
co_yield 1;

// 编译器展开后:
co_await promise.yield_value(1);

// 具体执行步骤:
1. 调用promise.yield_value(1):
   - 打印"捕获co_yield值:1,暂停协程";
   - 将1存入promise的value(堆上);
   - 返回suspend_always对象(Awaitable)。
2. 执行co_await suspend_always:
   - 调用await_ready() → 返回false(需要暂停);
   - 调用await_suspend() → 无操作,协程暂停;
   - 等待调用者resume()后,调用await_resume() → 无返回值。
3. 协程暂停,主线程可以通过gen.value()读取堆上的1。

总结(核心设计逻辑)

  1. Awaitable的设计初衷:将协程"暂停/恢复"的逻辑从编译器中剥离,交给用户自定义,实现通用化、可扩展的暂停策略(适配手动控制、异步自动恢复等所有场景);
  2. std::suspend_always/never的作用:是Awaitable的"极简基础实现",覆盖"无条件暂停"和"无条件不暂停"两种核心场景,避免用户重复编写冗余代码;
  3. co_yield的本质 :是co_await promise.yield_value(val)的语法糖,专门简化生成器场景的代码,其暂停逻辑完全依赖yield_value返回的Awaitable类型。

这些设计的核心逻辑是:编译器只负责协程的通用生命周期管理,具体的暂停/恢复策略由用户通过Awaitable类型定义------这也是C++20协程"灵活、可扩展"的关键。

相关推荐
寻寻觅觅☆9 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc9 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
l1t9 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿10 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar12310 小时前
C++使用format
开发语言·c++·算法
码说AI11 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS11 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
lanhuazui1011 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4411 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
星空下的月光影子11 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言