协程是 C++20 对“轻量级并发”的支持,旨在让异步编程更直观、更高效。它们可以在函数内部暂停执行、保存当前状态,并在之后恢复,避免了回调地狱与线程切换的开销。下面从概念、实现机制、关键语法以及典型使用场景等方面进行阐述。
一、协程的基本概念
-
暂停与恢复
协程在执行过程中遇到co_await、co_yield或co_return时会暂停。暂停点会保存所有局部状态(栈帧),之后可以随时恢复执行,直至再次暂停或结束。 -
协程对象
每个协程都有一个对应的 promise 对象,用于维护协程的状态、返回值以及异常处理。协程函数返回一个 task 对象,该对象本身不是异步任务,而是一个 awaitable(可被co_await的对象)。 -
Awaitable
任意对象,只要实现operator co_await()并返回一个 Awaiter,便可被co_await。C++20 标准库提供了std::suspend_always、std::suspend_never等简易 Awaiter。
二、协程的实现原理
C++20 的协程编译器会把协程函数拆解成以下几部分:
-
生成器函数体
变成一个普通函数,内部包含一个状态机。每次调用时会根据状态机的label继续执行到下一暂停点。 -
状态机
采用switch/case结构保存执行位置。每个暂停点对应一个case,在执行到暂停点前,程序会记录当前label,以便下次恢复。 -
协程框架
std::coroutine_handle用来控制协程的生命周期。它提供resume(),destroy(),done()等接口。
三、关键语法与标准库支持
| 关键字/类型 | 作用 | 例子 |
|---|---|---|
co_await |
暂停协程并等待 awaitable 完成 | auto val = co_await fetch_async(); |
co_yield |
产生一个值,暂停协程 | co_yield i++; |
co_return |
结束协程并返回值 | co_return result; |
std::suspend_always / std::suspend_never |
控制协程是否暂停 | co_await std::suspend_always(); |
std::coroutine_handle |
操作协程 | `auto h = std::coroutine_handle |
| ::from_promise(promise);` | ||
std::experimental::generator |
生成器类型(实验性) | `std::experimental::generator |
| seq();` |
简易协程示例
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task async_sleep(int ms) {
std::cout << "开始睡眠 " << ms << "ms\n";
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
co_return;
}
int main() {
auto t = async_sleep(1000);
t.get_return_object(); // 触发协程
std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // 等待完成
}
四、典型使用场景
-
网络 I/O
结合asio或自定义事件循环,协程可以直观地描述异步请求/响应流程,而不是嵌套回调。 -
异步生成器
co_yield用于实现流式数据生成器,例如遍历文件行、解析大数据流。 -
协作式多任务
在单线程环境下,用协程实现多任务调度,减少线程上下文切换。 -
延迟计算
对计算密集型任务使用co_await与std::async结合,实现懒加载与并行。
五、性能与注意事项
-
协程对象占用
协程的状态机存放在堆中,堆分配开销需要注意。可通过co_await std::suspend_always()等手段控制分配时机。 -
异常传播
若协程抛出异常,promise_type::unhandled_exception会被调用。可自定义异常捕获策略。 -
与标准库交互
C++20 尚在标准化阶段,部分实现可能存在细微差异。建议使用std::experimental::coroutine或 Boost.Coroutine 进行跨编译器兼容。
六、总结
C++20 协程为异步编程提供了极致的语义和效率。通过暂停/恢复机制、Promise/Coroutine Handle 等抽象,程序员可以以同步代码的形式书写异步逻辑,显著提升代码可读性和维护性。未来随着标准库的完善与编译器优化,协程将成为 C++ 高性能异步编程的核心工具。