在 C++20 标准中引入的协程(coroutine)机制为异步编程提供了一种既直观又高效的语法层面支持。相比传统的回调、状态机或第三方库(如 Boost.Asio、libuv),协程可以让代码保持同步的可读性,同时隐藏了复杂的状态管理。下面从概念、实现细节、典型使用场景以及性能考虑四个角度,来剖析 C++20 协程如何简化异步编程。
1. 协程基础
1.1 协程是什么
协程是一种“轻量级”子程序,它们可以在执行过程中暂停(co_await 或 co_yield)并在稍后恢复。协程的协作方式允许在同一线程内多次切换执行流,而不需要线程切换带来的上下文切换开销。
1.2 核心语法
co_await:暂停协程,等待一个 awaitable 对象完成。co_yield:在生成器中返回一个值并挂起,等待下一次请求。co_return:返回最终结果并结束协程。awaitable:实现了await_ready、await_suspend、await_resume三个成员函数的类型。
1.3 协程返回类型
C++20 对协程返回类型做了扩展。最常见的是:
- `std::future ` 或 `std::shared_future`(与 “ 兼容)
- `std::generator `(生成器)
- 自定义
promise_type的返回类型
2. 编写一个简单的 async 文件读取
#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
// awaitable 读取文件内容
struct FileReadAwaitable {
const std::string path;
std::string result;
bool await_ready() noexcept { return false; } // 总是异步
void await_suspend(std::coroutine_handle<> h) {
// 在一个线程池中执行实际读取
std::thread([this, h]() {
std::ifstream file(path);
std::stringstream ss;
ss << file.rdbuf();
result = ss.str();
h.resume(); // 读取完成后恢复协程
}).detach();
}
std::string await_resume() noexcept { return std::move(result); }
};
auto async_read(const std::string& path) -> FileReadAwaitable {
return FileReadAwaitable{path};
}
struct Task {
struct promise_type {
std::future<std::string> get_return_object() {
return std::future<std::string>{std::move(continuation)};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(std::string val) { result = std::move(val); }
void unhandled_exception() { std::terminate(); }
std::promise<std::string> continuation;
std::string result;
};
};
Task readFileAsync(const std::string& path) {
std::string content = co_await async_read(path);
co_return content;
}
上述示例展示了如何用 co_await 将文件读取包装成 awaitable,并在协程内部使用 co_return 返回最终内容。注意:这里的协程返回类型是自定义的 Task,内部使用 std::promise 作为协程的完成句柄。
3. 常见的协程模式
3.1 异步 I/O
使用 asio::awaitable 或 boost::asio::awaitable 作为 awaitable,直接在协程中 co_await I/O 操作。协程调度器会根据 I/O 完成事件恢复协程。
3.2 生成器
C++20 标准库提供了 `std::generator
`。适用于需要逐个产生值的场景,例如懒加载、无限序列。 “`cpp std::generator range(int start, int end) { for (int i = start; i pplx::task { return pplx::create_task([=]() -> std::string { // 异步查询数据库 std::string queryResult = co_await db_async_query(“SELECT * FROM users”); // 异步读取文件 std::string fileContent = co_await async_read(“welcome.html”); return queryResult + fileContent; }).then([request](std::string responseBody) { request.reply(web::http::status_codes::OK, responseBody); }); }); “` 虽然示例中使用了 `pplx::task`,但可以替换为 `std::future` 或自定义协程返回类型。关键点是 `co_await` 的使用,让异步流程像同步代码一样书写。 ## 7. 小结 C++20 的协程为异步编程提供了一种更自然、更接近同步语义的实现方式。通过 `co_await` 与 awaitable 对象,开发者可以将网络 I/O、文件操作、数据库查询等异步任务串联起来,保持代码可读性并降低错误率。结合事件循环、线程池以及第三方异步库,协程可以在高性能服务器、游戏引擎以及嵌入式系统等场景中发挥巨大的优势。 未来 C++ 标准会进一步完善协程的标准库支持(如 `std::ranges::generator`、`std::execution::par` 的协程友好实现),使得协程成为 C++ 异步编程的默认工具。对于正在考虑改用异步框架的项目,C++20 协程无疑是值得投入的一项技术。