在 C++20 标准中,协程(coroutines)被正式纳入语言层面,为编写异步代码提供了更直观、类型安全且高性能的方案。与传统的回调、promise/future 机制相比,协程可以让你像编写顺序代码一样写出非阻塞逻辑,极大降低代码的复杂度与错误率。
1. 协程基本概念
协程是一种“可挂起”的函数。通过 co_await、co_yield 和 co_return 等关键字,协程可以在执行过程中暂停并保存状态,随后在合适时机恢复执行。核心特性:
- 协程类型:`std::coroutine_handle ` 用来管理协程的生命周期。
- 悬挂点:
co_await、co_yield和co_return都是悬挂点,决定协程暂停的位置。 - 悬挂对象:
co_await后面跟随的对象需要满足 Awaitable 协议(实现await_ready、await_suspend、await_resume)。
2. 简单示例:异步读取文件
#include <iostream>
#include <coroutine>
#include <fstream>
#include <string>
struct AsyncFileReader {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
struct promise_type {
std::string buffer;
std::string file_path;
std::string& get_return_object() { return *this; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
AsyncFileReader(handle_type h) : handle(h) {}
~AsyncFileReader() { if (handle) handle.destroy(); }
handle_type handle;
};
AsyncFileReader readFileAsync(std::string path) {
std::ifstream file(path, std::ios::binary);
if (!file) throw std::runtime_error("Cannot open file");
std::string content((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
co_await std::suspend_always{}; // 模拟异步暂停
co_return;
}
int main() {
auto reader = readFileAsync("example.txt");
// 在这里可以继续执行其他任务
reader.handle.resume(); // 恢复协程
std::cout << "文件已读取完成" << std::endl;
}
上面代码仅作演示,实际使用时建议结合线程池或事件循环来真正实现异步 I/O。
3. 与标准库协程适配器
C++20 标准库提供了 std::future、std::async 与协程的桥接方式。常见模式:
std::future+co_await:co_await std::async([]{ return heavy_compute(); });- 自定义 awaitable:实现
await_ready、await_suspend、await_resume,可直接在协程中使用co_await。
4. 性能与错误处理
- 无栈拷贝:协程生成器不必在栈上复制所有局部变量,减少内存占用。
- 异常传递:协程通过
promise_type::unhandled_exception()把异常提升到协程外层,可与try/catch配合使用。 - 调试难度:协程的挂起与恢复可能导致堆栈帧不连续,调试时需使用支持 C++20 的调试器。
5. 进阶:自定义事件循环
在高性能服务器或游戏引擎中,常见做法是:
- 事件循环:循环调用
handle.resume(),当协程co_await一个等待对象时会挂起。 - 等待对象:如
TimerAwaitable、SocketAwaitable,在await_suspend中将协程注册到对应事件源。 - 事件回调:当事件到来后,调度器将对应协程恢复执行。
示例伪代码:
struct TimerAwaitable {
std::chrono::steady_clock::time_point when;
std::coroutine_handle<> awaiting;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) noexcept {
awaiting = h;
scheduler.schedule(when, h); // 注册到事件循环
}
void await_resume() noexcept {}
};
awaitable sleep_for(std::chrono::milliseconds ms) {
return TimerAwaitable{std::chrono::steady_clock::now() + ms};
}
6. 结语
C++20 的协程为编写高并发、低延迟的程序提供了强大工具。通过正确设计 awaitable、事件循环与错误处理机制,开发者可以在保持代码可读性的同时,获得接近系统底层性能的异步体验。随着编译器与标准库的不断完善,协程将在未来的 C++ 生态中扮演越来越核心的角色。