一、协程的核心概念:从“函数”到“可挂起的计算”

C++23的协程(Coroutines)​是一种“可挂起、可恢复”的函数——它能在执行过程中暂停(保存状态),后续再从暂停处继续执行。这与传统函数的“一次性执行到底”形成根本区别,完美适配惰性序列​(如斐波那契数列)、异步IO​(如非阻塞网络请求)等场景。

1. 协程的关键组件

理解协程,需掌握三个核心概念:

  • 协程函数​:返回协程对象的函数(用co_await/co_yield/co_return修饰);
  • Promise类型​:控制协程的生命周期(初始化、挂起、恢复、返回值);
  • 协程句柄(Coroutine Handle)​​:用于手动管理协程帧(如恢复、销毁)。

二、实战1:用std::generator实现惰性序列(斐波那契数列)

std::generator是C++23提供的基于协程的生成器,专门用于实现“惰性求值”——仅在需要时生成下一个元素,避免一次性计算所有值(节省内存,支持无限序列)。

1. 斐波那契数列生成器

#include <coroutine>
#include <generator>
#include <iostream>

// 返回一个生成<int>的协程
std::generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;       // 挂起协程,返回当前值a
        auto next = a + b;
        a = b;
        b = next;
    }
}

void test_fibonacci() {
    auto fib_gen = fibonacci();  // 创建生成器(协程未执行)
    
    // 逐步获取斐波那契数(每次推进协程)
    std::cout << "Fibonacci(前10项): ";
    for (int i = 0; i < 10; ++i) {
        std::cout << *fib_gen++ << " "; // *解引用取值,++推进协程
    }
    // 输出:0 1 1 2 3 5 8 13 21 34
}

2. std::generator的底层逻辑

  • 协程挂起​:co_yield a会将a存入生成器的“缓存”,并将协程暂停在当前位置;
  • 协程恢复​:调用++fib_gen时,协程从co_yield后继续执行,计算下一个值;
  • 内存高效​:仅需保存当前状态(ab),无需预计算所有元素。

三、实战2:自定义Promise类型——控制协程行为

std::generator封装了Promise类型的细节,但自定义协程需显式定义Promise类型,以控制初始化、挂起、返回值等行为。

1. 自定义协程:返回固定值

#include <coroutine>
#include <iostream>

// 1. 定义Promise类型(控制协程行为)
struct SimplePromise {
    int result;  // 存储协程的返回值

    // 初始挂起策略:协程启动后立即执行(不挂起)
    std::suspend_never initial_suspend() noexcept { return {}; }
    // 最终挂起策略:协程结束后不挂起(直接销毁)
    std::suspend_never final_suspend() noexcept { return {}; }
    // 异常处理:终止程序(简化示例)
    void unhandled_exception() noexcept { std::terminate(); }
    // 设置返回值:当协程执行co_return时调用
    void return_value(int val) noexcept { result = val; }
    // 创建Promise对象本身(默认构造)
    SimplePromise() = default;
};

// 2. 定义协程的返回对象(关联Promise类型)
struct SimpleCoroutine {
    using promise_type = SimplePromise;
    int value;

    // 从Promise句柄构造返回对象
    explicit SimpleCoroutine(std::coroutine_handle<SimplePromise> handle)
        : value(handle.promise().result) {}
};

// 3. 协程函数:返回SimpleCoroutine
SimpleCoroutine simple_coroutine() {
    co_return 42;  // 协程返回42(会调用Promise的return_value)
}

void test_simple_coroutine() {
    auto coro = simple_coroutine();
    std::cout << "Simple Coroutine Result: " << coro.value << std::endl;
    // 输出:42
}

2. 关键点解析

  • Promise类型​:定义了协程的“行为契约”——比如initial_suspend决定是否立即执行;
  • 协程返回对象​:通过Promise句柄获取协程的结果(handle.promise().result);
  • co_return​:协程的“返回语句”,会将值传递给Promise的return_value

四、实战3:用协程简化异步IO(模拟网络请求)

传统异步IO依赖“回调地狱”或“Future/Promise链”,而协程用同步写法实现异步,代码更简洁、易读。

1. 模拟异步网络请求

#include <coroutine>
#include <iostream>
#include <optional>
#include <thread>
#include <chrono>
#include <string>

// 1. 模拟异步操作的结果
struct AsyncData {
    std::string content;
};

// 2. 定义异步操作的Promise类型
struct AsyncPromise {
    AsyncData data;  // 存储异步结果

    std::suspend_always initial_suspend() noexcept { return {}; } // 启动后挂起(等待异步操作)
    std::suspend_always final_suspend() noexcept { return {}; }    // 结束后挂起(方便获取结果)
    void unhandled_exception() noexcept { std::terminate(); }
    void return_value(AsyncData d) noexcept { data = d; }
};

// 3. 定义异步操作的返回对象(句柄)
struct AsyncOperation {
    using promise_type = AsyncPromise;
    std::coroutine_handle<AsyncPromise> handle;

    explicit AsyncOperation(std::coroutine_handle<AsyncPromise> h) : handle(h) {}
    ~AsyncOperation() { if (handle) handle.destroy(); } // 手动销毁协程帧

    AsyncData get() { return handle.promise().data; } // 获取结果
};

// 4. 协程函数:模拟异步网络请求
AsyncOperation async_fetch(const std::string& url) {
    // 模拟网络延迟(实际中可替换为真实的异步IO操作,如epoll、io_uring)
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    // co_return异步结果(协程挂起,直到调用get()获取结果)
    co_return AsyncData{"Content from " + url};
}

// 5. 主函数:用协程处理异步请求
int main() {
    std::cout << "Start fetching data..." << std::endl;
    
    // 启动异步任务(协程挂起,主线程继续执行)
    AsyncOperation task = async_fetch("https://example.com");
    
    // 模拟主线程做其他事(如处理UI、计算)
    std::cout << "Do other work..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    // 获取异步结果(协程恢复,返回数据)
    AsyncData result = task.get();
    std::cout << "Received: " << result.content << std::endl;
    
    return 0;
}

2. 异步IO的优势

  • 同步写法​:用co_return代替回调,代码逻辑线性化;
  • 状态管理​:协程帧自动保存异步操作的状态(如url、delay时间);
  • 可组合性​:可将多个异步操作用co_await顺序组合(无需嵌套回调)。

五、C++23协程的“隐藏价值”

  1. 性能提升​:协程帧复用栈空间,避免传统递归或回调的内存开销;
  2. 代码可读性​:用同步写法实现异步,降低认知负担;
  3. 生态扩展​:为C++异步编程库(如Asio、Libuv)提供了更简洁的抽象层。

六、总结:协程是“下一代异步编程”的基石

C++23协程不是“语法糖”,而是重新定义了“函数”的边界——它让“暂停-恢复”成为一等公民,完美适配惰性序列、异步IO等场景。无论是用std::generator生成无限序列,还是用协程简化网络请求,都能感受到“代码更简洁、逻辑更清晰”的魅力。

一句话记住C++23协程​:

它让“等待”不再是阻塞,让“生成”不再是预计算——用同步的方式,写异步的代码。

参考资料​:

  • C++23标准草案—— Chapter 18 “Coroutines”;
  • 《C++23 Coroutines: A Practical Guide》(cppreference);
  • Bjarne Stroustrup的博客——“Coroutines: Making Asynchronous Programming Bearable”;
  • Louis Dionne的演讲——“C++20/23 Coroutines: From Theory to Practice”。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐