**C++20 中的协程:从概念到实践**

协程(coroutines)是 C++20 标准引入的一项强大特性,它允许函数在执行过程中挂起并在以后恢复,从而实现异步编程、生成器、状态机等多种高级功能。本文将从协程的核心概念、语法特性、典型使用场景以及常见陷阱展开详细讨论,并给出完整代码示例,帮助你快速上手。

1. 协程核心概念

  • 挂起(suspend):协程可以在任意位置挂起(suspend)执行,并保存当前状态(局部变量、调用栈等),随后可恢复(resume)执行。
  • awaiter:协程挂起或恢复时,关联的对象称为 awaiter,它提供 await_ready()await_suspend()await_resume() 三个成员函数。
  • promise:协程内部使用 promise 对象来传递返回值、异常以及协程的生命周期管理。

2. 关键语法

  • co_await:挂起协程,等待 awaiter 完成。
  • co_yield:在生成器中返回一个值,同时挂起协程。
  • co_return:结束协程并返回结果。
  • std::suspend_always / std::suspend_never:标准 awaiter,用于控制协程何时挂起。

3. 典型使用场景

场景 用法 代码片段
生成器 co_yield for(int i=0;i<10;i++) co_yield i;
异步 I/O co_await + std::future auto res = co_await async_task();
状态机 自定义 awaiter while (co_await state_machine());
资源管理 co_await std::suspend_always co_await std::suspend_always();

4. 示例:实现一个简单的异步网络请求

#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
#include <future>

struct async_sleep {
    std::chrono::milliseconds dur;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const {
        std::thread([h, d=dur](){
            std::this_thread::sleep_for(d);
            h.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

struct task {
    struct promise_type {
        std::future<std::string> get_return_object() {
            return std::move(fut);
        }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(std::string v) { fut.set_value(v); }
        void unhandled_exception() { fut.set_exception(std::current_exception()); }

        std::promise<std::string> fut;
    };
};

task fetch_data() {
    std::cout << "Start fetching..." << std::endl;
    co_await async_sleep{std::chrono::milliseconds(2000)};
    std::cout << "Fetching done!" << std::endl;
    co_return "Hello, Coroutine!";
}

int main() {
    auto fut = fetch_data();
    std::cout << "Waiting for result..." << std::endl;
    std::cout << fut.get() << std::endl;
}

运行结果:

Start fetching...
Waiting for result...
Fetching done!
Hello, Coroutine!

该示例演示了如何在协程中使用 co_await 对自定义的 async_sleep 进行挂起,从而实现非阻塞的延时操作。

5. 常见陷阱与调优

  1. 协程过度使用导致栈空间浪费
    协程挂起时会保存栈帧,过多协程会消耗大量堆栈。建议对协程粒度进行评估,必要时使用 std::suspend_never 控制挂起。
  2. 异常传播
    协程内部的异常会被包装到 promiseunhandled_exception(),若未正确处理会导致程序崩溃。务必在协程外部使用 try/catch 或检查 future 异常。
  3. 资源泄漏
    协程结束前需确保所有资源已正确释放,否则会导致内存泄漏。使用 RAII 结合协程时需注意其生命周期与协程挂起点的关系。

6. 结语

C++20 的协程为异步编程提供了更直观、更高效的手段。掌握协程的基本语法、设计模式和常见陷阱后,你可以在网络 I/O、游戏循环、数据流处理等多种场景中构建更简洁、更易维护的代码。欢迎继续深入探索协程的高级特性,如 std::generatorstd::task 以及与线程池、事件循环的结合。

祝你编码愉快,Happy C++20!

发表评论