一、前言
C++20 标准首次正式引入协程(coroutine)的语义,使得在单线程环境下也能轻松实现协作式异步编程。协程的核心特性是:可挂起、可恢复,与传统的回调或线程并发相比,它可以保持代码的同步直观性,显著降低错误率。
二、协程的基本概念
| 关键字 | 作用 |
|---|---|
co_await |
挂起协程,等待一个 awaitable 对象完成 |
co_yield |
把一个值返回给调用方,同时挂起协程 |
co_return |
结束协程,并可返回一个值 |
awaitable |
可被 co_await 的对象(实现 await_ready、await_suspend、await_resume) |
协程函数的返回类型必须是 `std::future
`、`std::generator` 或用户自定义类型 `task`,但编译器会把它们视为“协程体”,生成隐藏的状态机。 ## 三、为什么要使用协程 1. **代码可读性**:异步逻辑与同步逻辑写在同一函数中,易于跟踪。 2. **资源消耗低**:协程在挂起时不占用线程,减少上下文切换。 3. **错误处理简洁**:可用 `try-catch` 捕获异常,而不必在回调链中逐层传播。 ## 四、示例:异步 HTTP GET(无第三方库) 下面演示如何使用标准 C++20 协程与 `std::net`(假设已实现)完成一个简易的异步 HTTP GET。 “`cpp #include #include #include #include #include // 假设存在的标准网络库 // 1. 定义协程返回类型 template struct task { struct promise_type { T value_; std::exception_ptr exc_; auto get_return_object() { return task{std::coroutine_handle ::from_promise(*this)}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() { exc_ = std::current_exception(); } template void return_value(U&& v) { value_ = std::forward (v); } // awaitable interface for socket read auto await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) { // 注册 socket 读取完成后恢复协程 socket_.async_read([h](std::string&& data){ h.resume(); }); } T await_resume() { return std::move(value_); } }; std::coroutine_handle h_; task(std::coroutine_handle h) : h_(h) {} ~task() { if (h_) h_.destroy(); } T get() { if (h_.promise().exc_) std::rethrow_exception(h_.promise().exc_); return std::move(h_.promise().value_); } }; // 2. 简易异步 HTTP GET task http_get(const std::string& host, const std::string& path) { using namespace std::string_literals; socket_t sock; // 假设 socket_t 能 async_connect、async_write、async_read co_await sock.async_connect(host, “80”); // 挂起,等待连接 std::string req = “GET “s + path + ” HTTP/1.1\r\n” “Host: ” + host + “\r\n” “Connection: close\r\n\r\n”; co_await sock.async_write(req); // 挂起,等待写入完成 std::string resp; co_await sock.async_read(resp); // 挂起,等待读取响应 co_return resp; } int main() { auto t = http_get(“example.com”, “/”); std::string response = t.get(); // 阻塞直到协程完成 std::cout **说明** > – `async_connect/write/read` 需要实现 `await_ready/await_suspend/await_resume`。 > – 通过 `co_await` 挂起协程,等待异步操作完成后恢复。 > – 该示例不使用 Boost 或 ASIO,演示了协程与标准库的原生配合。 ## 五、协程与事件循环 协程本身并不提供事件循环;需要与 **I/O 多路复用**(如 `epoll`、`kqueue`)或现成的框架(Boost.Asio、libuv)结合。典型的架构: 1. **事件循环**:轮询 I/O 事件,完成后调用对应的协程恢复。 2. **协程调度**:将挂起的协程压入等待队列,事件到来时取出恢复。 ### 示例:基于 epoll 的简单调度器 “`cpp #include #include #include class scheduler { public: void run() { while (!tasks_.empty()) { int ready = epoll_wait(epoll_fd_, events_, events_.size(), -1); for (int i = 0; i (events_[i].data.ptr); ctx->resume(); // 恢复协程 } } } void add(task_context* ctx) { /* 注册 epoll 事件并将 ctx 存入等待队列 */ } private: int epoll_fd_ = epoll_create1(0); std::vector events_{1024}; }; “` ## 六、错误处理与异常传播 协程的异常传播与同步代码相同,使用 `try-catch`: “`cpp task process() { try { std::string data = co_await read_file(“data.txt”); // 处理数据 } catch (const std::exception& e) { std::cerr