在 C++ 20 中,协程(coroutine)正式成为语言标准的一部分,为编写异步代码和实现协作式多任务提供了更自然、更高效的手段。相比传统的基于线程或回调的异步编程模型,协程能够在保持同步代码可读性的同时,显著减少上下文切换和资源消耗。本文将从概念、实现原理、关键关键词以及一个简单示例四个方面,带你快速入门 C++ 20 协程。
1. 协程的基本概念
协程是一种“轻量级线程”,其核心特性是挂起(suspend)与恢复(resume)。程序在某个点可以主动挂起执行,把控制权交还给调用者;随后,当需要继续执行时,协程从挂起点恢复,继续执行后续代码。协程的执行状态(局部变量、程序计数器等)会被保存在栈之外的内存中,等待下次恢复。
协程适用于:
- 异步 I/O:在等待磁盘、网络等外部事件时挂起,避免阻塞线程。
- 生成器:一次产生一个值,类似 Python 的生成器。
- 并发任务:在多核环境下,协程可以在单线程中实现并行。
2. 协程的实现原理
C++ 协程的实现基于 C++ 20 标准库提供的 coroutine 关键字,底层实际上是生成器函数在编译时被拆解成一个 状态机。关键步骤如下:
- 定义协程函数:在函数签名前加
co_await、co_yield或co_return的关键字。 - 生成协程 promise:编译器为每个协程生成一个
promise_type,用于维护协程状态、返回值以及异常处理。 - 创建悬挂点:
co_await、co_yield会产生悬挂点,编译器把代码拆分成若干段,每段在协程挂起时停止执行。 - 调度恢复:当外部调用者或事件触发时,协程恢复执行,继续到下一个悬挂点。
C++ 标准库中并未提供具体的协程调度器,程序员需要自己实现或使用第三方库(如 cppcoro、Boost.Asio 等)。
3. 关键关键词与类型
| 关键词 | 用途 | 示例 |
|---|---|---|
co_await |
在协程中挂起,等待 awaitable 对象完成 | int x = co_await async_read(); |
co_yield |
在协程中生成一个值,类似生成器 | co_yield i; |
co_return |
结束协程并返回值 | co_return result; |
std::future/std::promise |
与协程配合使用的同步原语 | auto fut = async_function(); |
co_initial_suspend/co_final_suspend |
控制协程何时挂起与结束 | return std::suspend_never{} |
promise_type 结构
每个协程都有一个关联的 promise_type,典型成员:
struct my_coroutine_promise {
int value; // 存储返回值
std::exception_ptr eptr; // 异常处理
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() { return {}; }
void return_value(int v) { value = v; }
void unhandled_exception() { eptr = std::current_exception(); }
};
4. 一个完整示例:异步文件读取
下面给出一个最简易的异步文件读取协程示例。由于标准库不包含 I/O 协程实现,此处演示协程的基本结构和 promise 机制。
#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
// 1. 定义一个 awaitable 对象,模拟异步文件读取
struct async_file_reader {
struct promise_type {
std::string content;
async_file_reader get_return_object() {
return async_file_reader{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() { return {}; }
void return_value(const std::string& c) { content = c; }
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> handle;
async_file_reader(std::coroutine_handle <promise_type> h) : handle(h) {}
std::string get_content() { return handle.promise().content; }
void start() { handle.resume(); }
};
// 2. 协程函数,读取文件内容
async_file_reader read_file_async(const std::string& path) {
// 模拟 I/O 延迟
co_await std::suspend_always{};
std::ifstream file(path);
std::string data((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
co_return data;
}
// 3. 主函数
int main() {
auto reader = read_file_async("example.txt");
reader.start(); // 启动协程
std::string content = reader.get_content();
std::cout << "文件内容长度: " << content.size() << std::endl;
return 0;
}
说明
async_file_reader是一个 awaitable 类型,内部使用promise_type维护协程状态。read_file_async使用co_await挂起(这里仅演示,用suspend_always代替真正的异步等待),随后读取文件并co_return结果。- 主函数通过
reader.start()开始协程执行,随后获取结果。
5. 小结
- 协程让异步代码看起来像同步代码,显著提升可读性。
- C++ 20 的协程通过
co_await、co_yield、co_return实现挂起与恢复,底层由promise_type管理状态。 - 调度器不是标准的一部分,需自行实现或使用第三方库。
- 虽然语法上简洁,但使用时仍需注意异常、安全与资源管理。
希望本篇文章能帮助你快速入门 C++ 20 协程,并在实际项目中尝试使用。祝编码愉快!