在C++20标准中,协程(coroutine)作为一种强大的异步编程工具被正式纳入标准库。它们允许在函数内部挂起(suspend)和恢复(resume),从而实现非阻塞的并发操作。下面从底层实现的角度以及实际应用场景展开阐述。
1. 协程的基本概念
- 挂起点(suspend point):
co_await,co_yield,co_return等关键字所在的位置。 - 协程句柄(coroutine handle):
std::coroutine_handle<>,用于对协程进行控制(resume、destroy、检查是否完成)。 - 协程的状态机:编译器将协程编译成一个隐式的状态机,状态机的每个状态对应一次挂起点。
2. 协程的生成与执行流程
-
函数入口
当调用一个协程函数时,编译器会为其生成一个结构体,内部包含:- 所有本地变量(以帧的形式存储在堆栈或堆上,具体取决于是否需要悬挂)。
- 协程的状态指针(表示当前执行到哪个挂起点)。
- 返回值类型(如
std::future,generator等)。
-
协程句柄的创建
调用方得到一个std::coroutine_handle<>,并可以通过handle.resume()触发协程执行。 -
执行到挂起点
- 当执行到
co_await时,协程会检查待等待的对象是否已准备好。若未准备好,协程挂起,返回控制权给调用方;若已准备好,直接继续执行。 co_yield产生一个值,并挂起协程,等待下一个resume()。co_return结束协程,释放资源。
- 当执行到
-
状态机转移
每个挂起点后,编译器插入一段代码,将状态机指针指向下一个状态。这样在下次resume()时,直接跳转到对应的状态继续执行。
3. 协程的底层实现细节
-
堆与栈的分配
- 局部变量:若局部变量在整个协程生命周期内不被悬挂,则可保留在栈上;否则必须悬挂到堆上,以保持状态。
- 协程体:在第一次进入协程时,编译器会在堆上分配一个 协程状态对象(
promise_type的实例)。该对象维护协程的生命周期。
-
promise_type
promise_type是协程的核心,它定义了:get_return_object():返回协程句柄或包装类型。initial_suspend()/final_suspend():分别决定协程进入和结束时的挂起行为。return_value()/return_void():处理co_return的值。
-
异常传播
协程中的异常会被捕获并包装在promise_type中,随后在final_suspend()期间抛出。调用方可以通过handle.promise().get_exception()访问。
4. 应用场景
| 场景 | 说明 | 典型实现 |
|---|---|---|
| 异步IO | 用于非阻塞网络或磁盘IO,避免线程切换开销 | std::future, std::async 与自定义 io_context |
| 生成器 | 需要逐步产生序列数据 | `std::generator |
| ` | ||
| 协程流水线 | 多阶段处理,每阶段可以挂起 | 自定义管道协程 |
| UI线程 | 避免阻塞主线程 | UI框架提供协程事件循环 |
| 游戏循环 | 任务调度与等待 | 协程式事件系统 |
5. 示例代码:异步文件读取协程
#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
#include <vector>
struct async_file_reader {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
async_file_reader(handle_type h) : coro(h) {}
~async_file_reader() { if (coro) coro.destroy(); }
struct promise_type {
std::vector <char> buffer;
std::string filename;
std::exception_ptr eptr;
async_file_reader get_return_object() {
return async_file_reader(handle_type::from_promise(*this));
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { eptr = std::current_exception(); }
void return_value(std::vector <char>&& res) { buffer = std::move(res); }
};
};
async_file_reader read_file_async(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) co_return std::vector <char>{};
file.seekg(0, std::ios::end);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector <char> data(size);
file.read(data.data(), size);
co_return std::move(data);
}
int main() {
auto reader = read_file_async("example.txt");
std::cout << "File size: " << reader.coro.promise().buffer.size() << " bytes\n";
}
说明
async_file_reader封装了协程的promise_type与句柄。read_file_async在协程中读取文件,并在完成后返回缓冲区。- 调用方可以立即得到协程句柄,随后通过
coro.promise().buffer访问结果。
6. 小结
C++20协程通过编译器生成的状态机,将挂起与恢复细节隐藏在语言层面,极大提升了异步编程的可读性和可维护性。掌握其底层实现(堆栈分配、promise_type、状态转移)有助于我们更好地调优性能、处理异常以及构建高效的异步框架。随着标准库的进一步完善(如 std::generator, std::async 的协程化等),C++将成为更加强大、现代化的异步开发语言。