在C++20之前,异步编程常常需要使用回调、状态机或第三方库(如Boost.Asio)来实现。随着C++20对协程(coroutine)的官方支持,编写异步、懒加载和流式处理代码变得更加直观、可维护。本文从协程的核心概念、语法要点到一个完整的文件读取协程示例,带你快速上手。
1. 协程的核心概念
-
挂起点(yield)与恢复点(resume)
- 协程在
co_await,co_yield,co_return处暂停执行。 - 通过
handle.resume()重新激活协程。
- 协程在
-
协程句柄(coroutine_handle)
- 表示协程的运行时控制对象,负责管理协程的生命周期。
-
悬空与完成
handle.done()判断协程是否已完成。- 需要显式
handle.destroy()以释放资源。
-
返回类型
- 协程函数的返回类型是 `std::future ` 或自定义类型,内部实现会使用 `promise_type`。
2. 语法要点
| 关键字 | 用途 | 示例 |
|---|---|---|
co_await |
等待一个异步操作完成,挂起协程 | auto result = co_await asyncRead(); |
co_yield |
从协程返回一个值,挂起当前协程 | co_yield value; |
co_return |
返回最终结果并结束协程 | co_return finalResult; |
co_return + `std::optional |
||
| 用于可返回值或无值的协程 |co_return std::nullopt;` |
自定义返回类型
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) { current_value = value; return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Generator get_return_object() { return {handle_type::from_promise(*this)}; }
void return_void() {}
void unhandled_exception() { std::exit(1); }
};
handle_type coro;
explicit Generator(handle_type h) : coro(h) {}
~Generator() { if (coro) coro.destroy(); }
T next() {
coro.resume();
return coro.promise().current_value;
}
};
3. 实际案例:懒加载文件读取
以下示例演示如何使用协程逐行读取大文件,避免一次性加载到内存。
#include <coroutine>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
struct LineGenerator {
struct promise_type {
std::string current;
std::ifstream file;
LineGenerator get_return_object() {
return LineGenerator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(std::string&& line) {
current = std::move(line);
return {};
}
void return_void() {}
void unhandled_exception() { std::exit(1); }
};
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
explicit LineGenerator(handle_type h) : coro(h) {}
~LineGenerator() { if (coro) coro.destroy(); }
// 取得下一行
bool next(std::string& out) {
if (!coro.done()) {
coro.resume();
if (coro.done()) return false;
out = std::move(coro.promise().current);
return true;
}
return false;
}
};
LineGenerator read_lines(const std::string& path) {
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
co_yield std::move(line);
}
}
int main() {
std::string line;
for (auto gen = read_lines("large.txt"); gen.next(line); ) {
std::cout << line << '\n'; // 逐行处理
}
return 0;
}
关键点解析
read_lines是协程函数,返回LineGenerator。- 每次
co_yield时,协程挂起,保存当前行。 - 调用者通过
gen.next(line)触发resume,读取下一行。
4. 常见陷阱与建议
- 忘记
handle.destroy()- 协程句柄默认不销毁,必须手动释放,避免内存泄漏。
- 错误的
initial_suspend/final_suspend- 选择
suspend_always可以在调用时直接挂起,适合懒加载。
- 选择
- 异常处理
promise_type::unhandled_exception默认调用std::terminate,可根据需要自定义。
- 多线程协程
- 协程本身不是线程安全的,需在同一线程中调用
resume。
- 协程本身不是线程安全的,需在同一线程中调用
- 与
std::future混用- 若想与线程池配合,可让
co_await调用std::async返回的std::future。
- 若想与线程池配合,可让
5. 结语
C++20 协程为异步编程提供了更接近同步代码的语义,使得复杂的 IO、网络、状态机逻辑可以用更简洁、可读的方式实现。掌握协程的基本语法、返回类型设计与协程句柄的管理,是编写高性能、可维护 C++20 代码的关键。祝你在项目中愉快地探索协程的魅力!