协程(Coroutines)
  • 协程是一种可挂起和恢复的轻量级并发原语,它允许函数在执行过程中主动让出控制权并在之后从同一位置恢复执行,实现了用同步代码风格编写异步逻辑的能力
  • 与线程的系统级抢占式调度不同,协程采用用户态协作式调度,通过无栈设计将执行状态保存在堆分配的协程帧中,从而支持极低成本(KB级内存)的百万级并发
  • 通过"挂起-恢复"的无栈协程模型让开发者用同步的代码风格编写异步逻辑,彻底告别回调地狱
  • 其核心理念是提供零开销抽象的底层基础设施,而非完整的解决方案,将复杂的状态机管理和上下文切换交给编译器自动处理
  • 协程主要解决高并发场景下的性能瓶颈(支持百万级轻量级并发)和代码可维护性问题,特别在I/O密集型应用、惰性序列生成和复杂状态机等场景中发挥关键作用
  • 它为程序带来的核心优势包括:极低的内存开销(KB级)、高效的协作式调度、自然的异常处理和代码组合性,同时保持C++一贯的性能优势和灵活性,使开发者能够在保持代码清晰直观的同时实现极高的并发性能
  • 代表了现代C++对异步编程范式的重大革新
一、基础用法

学习一个东西,我一般会先学会用,然后再深入理解学会用,两个学会用,什么意思呢?哈哈!1、学会基础使用语法,2、学会将其灵活运用到实际开发工作中,下面我们先来讨论一下第一个学会吧。

说到协程,我们首先会想到的是他的六大重要元素:三个核心关键字co_await、co_yield、co_return;一个句柄std::coroutine_handle,两个重要结构体Awaitable、Generator

1.1 Generator

Generator 我们可以理解其是我们自定义的一个特殊的协程,它可以在每次被调用时生成一个值并暂停,下次调用时从上一次暂停的地方继续执行。(C++20 给我们提供了std::generator)

下面我们先来看一个最基本的自定义协程示例代码

// 即使最简单的协程也需要自定义类型
struct simple_generator {
    struct promise_type {
        simple_generator get_return_object() { 
            return simple_generator {}; 
        }
        auto initial_suspend() { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_never{}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

simple_task example() {
    std::cout << "Hello";
    co_return;  // 必须返回自定义的协程类型
}

下面是使用使用 std::generator(C++20)的示例代码

#include <generator>  // C++23

// C++23 可以直接使用 std::generator
std::generator<int> fibonacci(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int next = a + b;
        a = b;
        b = next;
    }
}

int main() {
    for (int value : fibonacci(10)) {
        std::cout << value << " ";
    }
}

我们先来看看C++标准对Generator 的定义/约束(伪代码)

template<typename G>
concept Generator =
// 基础要求
  requires { typename G::promise_type; }&& // 必须遵循
std::ranges::input_range<G>&& //范围约束-->非必须,根据应用决定

// 生命周期管理-->建议遵循
std::movable<G> &&
!std::copyable<G>&&
std::is_nothrow_destructible_v<G>&&

// 迭代支持-->非必须,根据应用决定
    requires(G g) {
        { g.begin() } -> std::input_iterator;
        { g.end() } -> std::sentinel_for<decltype(g.begin())>;
        { g.empty() } -> std::same_as<bool>;
        { bool(g) } -> std::same_as<bool>;
};

从上面可以看出,Generator 最直接的约束是必须具有G::promise_type,那么promise_type右是什么呢?

promise_type 是协程的控制中心和状态管理器,它定义了协程如何创建、暂停、恢复、返回值和处理异常。
核心作用:

  • 创建协程对象 - get_return_object() 返回给调用者的协程控制对象
  • 控制生命周期 - initial_suspend()/final_suspend() 决定何时开始和结束
  • 处理值传递 - return_void()/return_value()/yield_value() 处理协程的返回和产出
  • 异常安全 - unhandled_exception() 处理协程内部未捕获的异常
  • 定制化 - await_transform() 可以拦截和转换所有的 co_await 表达式
  • 本质:promise_type 是编译器与用户代码之间的契约接口,编译器通过它来控制协程,用户通过它来定制协程行为。每个协程函数都有一个对应的 promise_type 实例,存储在协程帧中,管理着协程的整个生命周期和状态转换。

下面是G::promise_type的约束(伪代码)

// 编译器对 promise_type 的要求(伪代码)
template<typename P>
concept PromiseType = requires(P p) {
    // 必须的方法
    { p.get_return_object() } -> std::same_as<ReturnType>;
    { p.initial_suspend() } -> Awaitable;
    { p.final_suspend() } noexcept -> Awaitable;
    p.unhandled_exception();
    
    // 可选的方法(至少实现一个)
    requires requires { p.return_void(); } ||
             requires(T value) { p.return_value(value); };
    
    // 可选:用于 co_yield
    requires requires(Y value) { p.yield_value(value); } -> Awaitable;
    
    // 可选:用于转换 co_await
    requires requires(A a) { p.await_transform(a); } -> Awaitable;
};

从上面的定义中我们可以看出,我们必须实现get_return_object、initial_suspend、final_suspend等方法,且其返回值必须满足Awaitable的约束。必须实现unhandled_exception方法,必须实现return_void或者return_value方法。下面我们来分别看看各个方法:

get_return_object() 的约束

 // 返回类型必须能被协程函数的返回类型构造
    Generator get_return_object() {
        // 通常用法:
        return Generator{
            std::coroutine_handle<promise_type>::from_promise(*this)
        };
    }

暂停控制器的约束

// 必须返回一个 awaitable 类型
    auto initial_suspend() {
        // 常见的返回类型:
        return std::suspend_always{};    // 立即暂停
        // return std::suspend_never{};   // 立即开始执行
    }
    
    // 必须标记为 noexcept
    auto final_suspend() noexcept {
        return std::suspend_always{};
    }
    // initial_suspend() 之后:协程状态已分配,但可能暂停
	// final_suspend() 之后:协程即将销毁

返回值的协议

 // 协议1:如果协程使用 co_return;(无值)
    void return_void() {
        // 处理无返回值的结束
    }
    
    // 协议2:如果协程使用 co_return value;
    template<typename T>
    void return_value(T&& value) {
        // 存储返回值
        result_ = std::forward<T>(value);
    }
// 使用示例:
Generator coro1() {
    co_return;  // 调用 return_void()
}

Generator<int> coro2() {
    co_return 42;  // 调用 return_value(42)
}

yield_value 的协议

struct promise_type {
	...
    // 必须返回一个 awaitable 类型
    auto yield_value(int value) {
        // 1. 存储生成的值
        current_value_ = value;
        
        // 2. 返回暂停控制器
        return std::suspend_always{};
        
        // 或者可以返回其他 awaitable:
        // return some_other_awaitable{};
    }
    ...
};

// yield_value 的重载
struct promise_type {
	...
    // 支持多种类型
    auto yield_value(int value) {
        current_value_ = value;
        return std::suspend_always{};
    }
    
    auto yield_value(const char* str) {
        current_value_ = std::string(str);
        return std::suspend_always{};
    }
    ...
};

unhandled_exception的协议

 // 关键:当协程抛出未捕获异常时自动调用
        void unhandled_exception() {
            std::cout << "unhandled_exception() 被调用!" << std::endl;
            std::cout << "异常类型: " 
                      << typeid(std::current_exception()).name() << std::endl;
        }

await_transform的协议

  // 当遇到 co_await expr 时,编译器首先检查:
    // 1. promise.await_transform(expr) 是否存在
    // 2. 如果存在,使用其返回值替代 expr
    // 3. 否则,继续正常处理 expr
    
    template<typename T>
    auto await_transform(T&& expr) {
        // 可以修改、包装或替换原始的 awaitable
        return TransformedAwaitable{std::forward<T>(expr)};
    }
1.2 Awaitable

Awaitable 是可等待对象的三元接口协议,它定义了 co_await 表达式的挂起、恢复和结果获取机制。
核心作用:

  • 挂起控制 - await_ready() 决定是否需要暂停协程
  • 异步调度 - await_suspend() 安排协程何时恢复(返回 void/bool/coroutine_handle)
  • 结果传递 - await_resume() 提供 co_await 表达式的结果值
  • 本质:Awaitable 是协程与异步操作之间的桥梁,它将任意的异步操作(I/O、定时器、计算等)统一封装成协程可以理解和等待的标准接口。通过实现这个三元协议,任何类型都可以被 co_await,从而将异步操作无缝集成到协程的线性控制流中。

一样的,我们先来看看C++标准对Awaitable 的定义(伪代码)

template<typename T>
concept Awaitable = requires(T t, std::coroutine_handle<> h) {
    // await_ready 必须返回 bool,且不抛出(通常)
    { t.await_ready() } noexcept -> std::same_as<bool>;

    // await_suspend 有三种可能的返回类型
        requires requires {
            { t.await_suspend(h) } noexcept -> std::same_as<void>;
    } || requires {
        { t.await_suspend(h) } noexcept -> std::same_as<bool>;
    } || requires {
        { t.await_suspend(h) } noexcept -> std::same_as<std::coroutine_handle<>>;
    };

    // await_resume 可返回任意类型
    t.await_resume();
};

从上面我们可以清楚的看出,我们必须实现await_ready、await_suspend、await_resume方法,以及他们各自的约束

await_ready() 的详细规则

 // 规则1:必须是 const noexcept(推荐)
    bool await_ready() const noexcept {
        // true:  跳过 await_suspend,直接调用 await_resume()
        // false: 需要暂停,调用 await_suspend()
        return is_completed;
    }
    
    // 规则2:快速检查
    // 不能有副作用,不能阻塞
    // 通常只是检查标志位或简单状态

await_suspend() 的详细规则

  // 返回 void:协程被挂起,调用者继续执行
    void await_suspend(std::coroutine_handle<> h) noexcept {
        // 可以安排异步操作
        schedule_resume_later(h);
        
        // 返回后,协程栈帧被保存
        // 控制权返回给调用者/恢复者
    }
// 返回 bool:
    // true  - 协程已暂停(标准行为)
    // false - 协程不应暂停,立即恢复执行
    bool await_suspend(std::coroutine_handle<> h) noexcept {
        if (should_suspend_) {
            schedule_resume_later(h);
            return true;  // 协程已暂停
        } else {
            return false;  // 不要暂停,立即继续
        }
    }
 // 返回 coroutine_handle:
    // 当前协程暂停,立即恢复返回的句柄
    std::coroutine_handle<> await_suspend(
        std::coroutine_handle<> current
    ) noexcept {
        // 这是一种尾调用优化,不会增加栈深度
        
        // 可以做清理工作
        cleanup(current);
        
        // 返回要恢复的协程
        return next_coro;
        
        // 等价于:
        // next_coro.resume();
        // return;  // 当前协程被销毁
    }
   // 参数类型必须是 std::coroutine_handle<>
    // <> 中的模板参数通常是 void
    void await_suspend(std::coroutine_handle<> h) noexcept {
        // h 是等待当前 awaitable 的协程句柄
        // 可以存储 h 以便稍后恢复
        
        // h 的类型实际上是:
        // std::coroutine_handle<promise_type>
        // 其中 promise_type 是协程的 promise 类型
        
        // 可以通过 h 访问 promise:
        // auto& promise = h.promise();
        // 可以存储结果、设置异常等
    }

await_resume() 的详细规则

// 可以返回任意类型

struct VariousReturnsAwaitable {
    // 返回 void
    void await_resume() noexcept {}
};

struct IntReturnsAwaitable {
    int await_resume() noexcept { return 42; }
};

struct ComplexReturnsAwaitable {
    std::pair<int, std::string> await_resume() noexcept {
        return {42, "answer"};
    }
};

// 使用
Task<> example() {
    co_await VariousReturnsAwaitable{};  // 无返回值
    
    int value = co_await IntReturnsAwaitable{};
    
    auto [num, str] = co_await ComplexReturnsAwaitable{};
    
    co_return;
}
1.3 co_await

协程中用于暂停当前协程,等待异步操作完成的关键字

基本语法:

// 基本语法
co_await expression;

示例:

// 示例:等待一个异步任务
MyGenerator<int> async_compute() {
    std::cout << "开始计算..." << std::endl;
    
    int result = co_await async_operation();  // 暂停,等待操作完成
    
    std::cout << "计算完成: " << result << std::endl;
    co_return result;
}

expression遵循Awaitable 类型的三元协议

  • 已经是 Awaitable 对象(实现了三个 await_* 方法)
  • 重载了 operator co_await 的类
  • 通过 promise_type::await_transform 转换
1.4 co_yield 生成值并暂停
// co_yield:生成一个值给调用者,然后暂停等待下一次请求
Generator<int> fibonacci() {
    int a = 0, b = 1;
    
    while (true) {
        co_yield a;  // 生成当前值,暂停
        
        // 下次被请求时从这里继续
        int next = a + b;
        a = b;
        b = next;
    }
    // 注意:这个协程永远不会结束(无co_return)
}
1.5 co_return 协程返回
Generator<int> compute() {
    int result = perform_calculation();
    co_return result;  // 协程返回
}
1.6 std::coroutine_handle

std::coroutine_handle 是协程的唯一标识符和控制手柄,它提供了对协程的生命周期管理(恢复、销毁)和状态查询(完成状态、promise 访问)的能力。

核心作用:

  • 协程控制:恢复执行、销毁协程
  • 状态查询:检查协程是否完成
  • Promise 访问:获取和操作 promise 对象
  • 内存管理:获取协程帧地址
  • 类型擦除:void 特化提供类型无关的操作
 // 具体类型的句柄
 std::coroutine_handle<promise_type> handle;
二、完整示例
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
#include<optional>
#include<windows.h>

template<typename T>
struct Awaitable {
    // 1. 检查是否已经就绪
    bool await_ready() const noexcept
    {
        std::cout << "await_ready被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        return false;
    };

    // 2. 暂停时的操作
    //    返回 void, bool, 或 coroutine_handle<>
    void await_suspend(std::coroutine_handle<> awaiting_coro) noexcept
    {
        Sleep(1000);
        std::cout << "await_suspend被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        //awaiting_coro.resume();
    };

    // 3. 恢复时的操作
    T await_resume() noexcept
    {
        std::cout << "await_resume被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        return _value;  // 这个值会成为 co_await 表达式的值
    };  

    T _value;
};

template<typename T>
struct Generator
{
    struct promise_type {

        // 存储当前生成的值
        std::optional<T> current_value_;

        // [必须] 创建协程对象
        Generator get_return_object()
        {
            // 在这里添加一些业务外面吧!!!

            std::cout << "get_return_object被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
            return Generator{ std::coroutine_handle<promise_type>::from_promise(*this)};
        };

        // [必须] 初始暂停策略
        auto initial_suspend() 
        {
            // 在这里添加一些业务外面吧!!!

            std::cout << "initial_suspend被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
            return std::suspend_never{};
        };

        // [必须] 最终暂停策略
        auto final_suspend() noexcept
        {
            // 在这里添加一些业务外面吧!!!

            std::cout << "final_suspend被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
            return std::suspend_always{};

            /*
            * 这里需要注意一下:如果我们返回std::suspend_never,协程帧(coroutine frame)会在 final_suspend() 返回时立即自动销毁,但 std::coroutine_handle 对象本身(即你的 handle_ 变量)不会自动变为 nullptr,这个句柄变成了一个悬空句柄(dangling handle)
            */
        };

        // [必须] 异常处理
        void unhandled_exception()
        {
            // 在这里添加一些业务外面吧!!!

            std::cout << "unhandled_exception被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;

            // 方式1:存储异常
            //exception_ = std::current_exception();

            // 方式2:立即处理
            try {
                std::rethrow_exception(std::current_exception());
            }
            catch (const std::exception& e) {
                std::cerr << "协程异常: " << e.what() << '\n';
            }

            // 方式3:终止程序
            // std::terminate();
        };

        // [必须最少有一个] 返回处理,如果协程通过异常结束,两个都不需要

        // // co_return
        //void return_void() {
        //    // 对于生成器,通常以 co_return; 结束,不返回值
        //    std::cout << "return_void被执行了,执行的线程ID[" << std::thread::get_id() << "]" << std::endl;
        //};

        // co_return value;
        void return_value(T value)
        {
            current_value_ = std::move(value);
            std::cout << "return_value被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        } ;            



        // [可选] 生成值处理
        auto yield_value(T value)              // co_yield value;
        {
            current_value_ = std::move(value);
            std::cout << "yield_value被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;

            return std::suspend_always{};  // 每次 yield 后都暂停
        };

        // [可选] 自定义await转换
        template<typename T1>
        auto await_transform(T1 val)
        {
            Awaitable<T1> awaitable;
            awaitable._value = val;
            std::cout << "await_transform被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
            return awaitable;
        };

    };

    using handle_type = std::coroutine_handle<promise_type>;

    // 移动构造
    Generator(Generator&& other) noexcept
        : handle_(std::exchange(other.handle_, nullptr)) {
        std::cout << "Generator(Generator&& other) noexcept 被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    }

    // 禁止拷贝
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;

    // 移动赋值
    Generator& operator=(Generator&& other) noexcept {
        std::cout << "Generator& operator=(Generator&& other) noexcept 被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        if (this != &other) {
            if (handle_) handle_.destroy();
            handle_ = std::exchange(other.handle_, nullptr);
        }
        return *this;
    }

    // RAII析构
    ~Generator() {
        std::cout << " ~Generator() 被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        if (handle_) {
            handle_.destroy();
        }
    }


    // 值访问的安全检查
    T value() const {
        if (!handle_ || !handle_.promise().current_value_) {
            throw std::runtime_error("No current value");
        }
        return *handle_.promise().current_value_;
    }

    //  迭代控制
    bool next() {
        if (!handle_ || handle_.done()) {
            return false;
        }

        std::cout << std::endl << "i.next() 线程ID[" << std::this_thread::get_id() << "]" << std::endl;
        handle_.resume();
        return !handle_.done();
    }

private:
    explicit Generator(handle_type h) : handle_(h) 
    {
        std::cout << "Generator(handle_type h) : handle_(h) 被执行了,执行的线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    }

    handle_type handle_;

    //std::exception_ptr exception_;
};

// 使用
Generator<int> example() 
{
    Awaitable<int> awaitable;
    awaitable._value = 100;

    std::cout << std::endl << "start co_await awaitable 线程ID[" << std::this_thread::get_id() << "]" << std::endl ;
    co_await awaitable;

    std::cout << std::endl << "start co_yield 30 线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    co_yield 30;

    std::cout << std::endl << "start co_await awaitable 线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    co_await awaitable;

    std::cout << std::endl << "co_return 45 线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    co_return 45;
}

int main(int argc, char* argv[])
{
    std::cout << "主线程ID[" << std::this_thread::get_id() << "]" << std::endl;

    std::cout << std::endl << "start example 线程ID[" << std::this_thread::get_id() << "]" << std::endl ;
    auto i = example();
    std::cout << std::endl << "end example 线程ID[" << std::this_thread::get_id() << "]" << std::endl;
    while (i.next())
    {
        
    }
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐