协程是 C++20 引入的重要语言特性之一,它让异步编程、并发以及事件驱动程序的编写变得更加自然和高效。本文将从协程的基本概念、实现机制、典型使用场景以及实际代码示例四个方面,帮助你快速上手并深入理解 C++20 协程。
1. 协程概念回顾
协程是一种能够在执行过程中“挂起”和“恢复”的函数。它与传统的函数不同,协程可以在中途暂停,随后从暂停点继续执行,而不必一次性完成整个调用。协程的核心思想是将函数拆分为多个暂停点(co_await、co_yield、co_return),每个暂停点都能在需要时保存状态,随后恢复。
在 C++20 中,协程由三大关键字实现:
co_await:等待一个 awaitable 对象完成,类似await。co_yield:在协程中产生一个值,类似yield。co_return:结束协程并返回一个结果。
协程并不是一种“线程”,它们在同一个线程中运行,只是通过协作方式实现了异步行为。协程的真正异步实现依赖于底层的 Awaitable 对象(如 std::future、自定义的异步 I/O 对象等)。
2. 协程的实现原理
协程在编译期被转换为状态机。C++ 编译器会把协程体拆分成若干个基本块,并生成一个隐藏的 promise_type,负责维护协程的状态、结果以及异常处理。
2.1 promise_type
promise_type 是协程的核心,它定义了协程运行时需要的接口:
struct my_promise {
int value;
std::exception_ptr eptr;
auto get_return_object() { return coroutine_handle <my_promise>::from_promise(*this); }
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
void unhandled_exception() { eptr = std::current_exception(); }
void return_value(int v) { value = v; }
};
编译器会根据 promise_type 的实现,自动生成协程入口、挂起点、恢复点以及销毁逻辑。
2.2 Awaitable 对象
任何可以被 co_await 的对象都必须满足 Awaitable 协议,即实现 await_ready、await_suspend、await_resume 三个成员函数。await_ready 用来判断是否需要挂起,await_suspend 在挂起时被调用并返回一个 std::coroutine_handle 或 bool,await_resume 在协程恢复时返回值。
struct async_task {
int value;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 这里可以把 h 存储到线程池或事件循环中,等待异步事件
}
int await_resume() { return value; }
};
3. 协程的典型使用场景
-
异步 I/O
通过协程配合事件驱动库(如 Boost.Asio、libuv)实现高并发的网络服务器。协程可以让异步 I/O 代码像同步代码一样直观。 -
协作式多任务
在游戏循环、图形渲染等需要分帧执行的场景,协程可以按需切换任务,避免线程上下文切换的开销。 -
流式数据处理
co_yield让协程成为轻量级的生成器,适合处理大规模或无穷序列(如文件读取、日志处理)。 -
延迟执行
使用co_await std::suspend_always{}或自定义延迟对象,让协程在特定条件下暂停,适合实现定时器、延迟任务等。
4. 实战示例:异步文件读取
下面演示一个简易的异步文件读取协程,使用 std::filesystem 与 std::ifstream 读取文件,并利用自定义 Awaitable 对象实现异步等待。
#include <iostream>
#include <fstream>
#include <string>
#include <coroutine>
#include <future>
struct async_read {
std::string filename;
std::string buffer;
struct awaiter {
std::string& buffer;
std::string filename;
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::async(std::launch::async, [this, h]() {
std::ifstream file(filename, std::ios::binary);
buffer.assign((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
h.resume();
});
}
std::string await_resume() noexcept { return buffer; }
};
awaiter operator co_await() const { return {buffer, filename}; }
};
struct async_task {
struct promise_type {
async_task get_return_object() {
return async_task{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() noexcept {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> h;
explicit async_task(std::coroutine_handle <promise_type> h) : h(h) {}
~async_task() { if (h) h.destroy(); }
};
async_task read_file(const std::string& path) {
async_read reader{path};
std::string data = co_await reader;
std::cout << "File size: " << data.size() << " bytes\n";
co_return;
}
int main() {
read_file("example.txt");
std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待异步完成
return 0;
}
代码说明:
async_read定义了一个 Awaitable 对象,在await_suspend中使用std::async异步读取文件,读取完成后恢复协程。async_task是一个简单的协程包装器,使用promise_type来控制协程的生命周期。read_file是协程函数,它co_await异步读取器,并在读取完成后输出文件大小。
此示例演示了协程与标准库中的 std::async 配合使用的典型模式。实际项目中可以将 await_suspend 替换为网络 I/O 或数据库访问的异步回调,以实现真正的异步服务器。
5. 结语
C++20 协程为语言带来了强大的异步编程能力。它将复杂的回调链简化为直观的顺序代码,减少了错误并提升了可维护性。掌握协程的基本原理与使用场景后,你可以在网络、游戏、嵌入式系统等多种领域快速构建高性能、可伸缩的应用程序。祝你在 C++ 协程的世界里玩得开心,写出更优雅的代码!