协程(Coroutines)是C++20标准引入的一种强大且灵活的语言特性,旨在简化异步编程、并发处理以及需要暂停/恢复执行的场景。与传统的回调函数、线程或事件循环不同,协程在语义上更贴近同步代码,既能提升代码可读性,又能保持高效性能。本文将从概念、实现机制、典型使用场景以及最佳实践等方面,系统阐述C++20协程的核心内容,并给出实用示例。
1. 协程的基本概念
协程是一种可以在函数执行过程中“暂停”并在稍后“恢复”的特殊函数。与线程相比,协程不需要系统级上下文切换,成本更低;与回调函数相比,协程的代码更像直线式的顺序执行,减少了回调地狱。
C++20通过三个核心概念实现协程:
co_await:等待一个异步操作完成,并将结果返回给协程。co_yield:产生一个值,协程的调用者可以迭代获取。co_return:返回协程最终的结果。
协程函数在编译时被转换成一个状态机。函数体中的每个co_await、co_yield、co_return都对应一个“暂停点”。调用者每次恢复协程时,执行到下一个暂停点。
2. 协程的实现细节
2.1 协程句柄
协程的运行状态由句柄(std::coroutine_handle<>)管理。句柄包含协程的入口地址、栈帧指针以及协程对象自身。通过句柄可以:
resume():恢复协程执行。destroy():销毁协程(释放资源)。done():判断协程是否已结束。
2.2 协程 promise
promise 是协程的“枢纽”。它定义了协程的生命周期事件,例如:
get_return_object():返回协程对象,通常是一个包装类型,内部持有coroutine_handle。initial_suspend()与final_suspend():分别决定协程开始和结束时的挂起行为。return_value/unhandled_exception():处理返回值或异常。
自定义 promise 可以实现多种协程模型,如可迭代协程、异步任务、事件流等。
3. 典型协程模型
3.1 异步任务(Task)
最常见的模型是 **`Task
`**,表示异步计算最终返回 `T`。示例: “`cpp #include #include #include #include struct Task { struct promise_type { Task get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; }; Task async_delay(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); co_return; } “` ### 3.2 可迭代协程(Generator) 利用 `co_yield` 实现惰性序列: “`cpp template struct Generator { struct promise_type { T current_value; Generator get_return_object() { return Generator{std::coroutine_handle ::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } std::suspend_always yield_value(T value) { current_value = value; return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle handle; Generator(std::coroutine_handle h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } struct iterator { std::coroutine_handle h; bool done = false; iterator(std::coroutine_handle h_) : h(h_) { ++(*this); } iterator& operator++() { if (!h.done()) h.resume(); done = h.done(); return *this; } T operator*() const { return h.promise().current_value; } bool operator==(std::default_sentinel_t) const { return done; } }; iterator begin() { return iterator{handle}; } std::default_sentinel_t end() { return {}; } }; Generator count(int n) { for (int i = 0; i async_read_file(const std::string& path) { std::ifstream f(path); std::string line; while (std::getline(f, line)) co_yield line; } “` ## 4. 与第三方库的结合 – **Boost.Asio**:C++20 协程已被 Asio 官方支持,可通过 `co_spawn` 直接创建协程。 – **libuv**:提供异步 I/O,协程可包装 `uv_async_t` 或 `uv_fs_t`。 – **cppcoro**:一个现代协程库,提供 `task`, `generator`, `timer`, `async_pipe` 等工具。 ## 5. 性能与注意事项 1. **栈使用**:协程使用的是线程栈,暂停时保存局部变量在栈上,若协程频繁切换,栈帧可能较大。可使用 `std::suspend_always` 或 `suspend_never` 优化挂起行为。 2. **对象移动**:在协程内部返回大对象时,应使用 `co_return std::move(obj)`,避免拷贝。 3. **异常传播**:未捕获的异常会导致协程终止,务必在 promise 的 `unhandled_exception` 中妥善处理。 4. **调试**:IDE 对协程的支持逐渐完善,但仍可能出现堆栈跟踪不直观的问题。 ## 6. 实战案例:简易异步 HTTP 客户端 以下示例结合 `Boost.Beast` 与 C++20 协程,实现一个简易的 HTTP GET 请求: “`cpp #include #include #include #include #include #include namespace asio = boost::asio; namespace beast = boost::beast; using asio::ip::tcp; using asio::awaitable; using asio::co_spawn; using asio::detached; awaitable async_http_get(const std::string& host, const std::string& target) { auto executor = co_await asio::this_coro::executor; tcp::resolver resolver{executor}; auto const results = co_await resolver.async_resolve(host, “http”, asio::use_awaitable); beast::tcp_stream stream{executor}; co_await stream.async_connect(results, asio::use_awaitable); beast::http::request req{beast::http::verb::get, target, 11}; req.set(beast::http::field::host, host); req.set(beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING); co_await beast::http::async_write(stream, req, asio::use_awaitable); beast::flat_buffer buffer; beast::http::response res; co_await beast::http::async_read(stream, buffer, res, asio::use_awaitable); stream.socket().shutdown(tcp::socket::shutdown_both, ec); co_return beast::buffers_to_string(res.body().data()); } int main() { asio::io_context ioc{1}; co_spawn(ioc, []() -> awaitable { std::string body = co_await async_http_get(“www.example.com”, “/”); std::cout