C++20 标准引入了协程(coroutine)这一强大特性,使得异步编程与生成器模式在语言层面得到了原生支持。相比传统的基于回调或线程的异步实现,协程提供了更简洁、可读性更高的代码结构。本文将从协程的内部机制、使用方式以及典型应用场景等方面进行探讨,帮助读者快速上手并掌握 C++20 协程。
1. 协程基础概念
1.1 什么是协程?
协程是一种轻量级的用户级线程,它允许在执行过程中挂起和恢复。与线程不同,协程在单线程环境下实现并发,并且切换开销极低。C++20 协程使用 co_await, co_yield, co_return 关键字来实现挂起和恢复。
1.2 协程的生命周期
- 创建:编译器把协程函数编译成状态机。每个挂起点生成一个
promise对象。 - 开始:第一次调用协程函数会返回一个
std::coroutine_handle,协程进入暂停状态。 - 挂起/恢复:通过
co_await、co_yield触发挂起,协程暂停执行;通过外部resume()恢复执行。 - 结束:执行到
co_return或函数尾部时,协程完成并释放资源。
2. 关键组件与实现细节
2.1 promise_type
promise_type 是协程的核心,负责管理协程的状态、返回值、异常等。标准库提供了默认实现,开发者可根据需要自定义。常见成员:
initial_suspend():决定协程开始时是否挂起。final_suspend():决定协程结束后是否挂起。get_return_object():返回协程句柄或包装类型。return_value()/return_void():处理co_return。
2.2 std::suspend_always 与 std::suspend_never
这两个结构体用于指定挂起行为:
std::suspend_always:始终挂起。std::suspend_never:永不挂起。
2.3 状态机的生成
编译器把协程函数拆分为多个基本块,并生成跳转表。每个 co_await 或 co_yield 处的代码块相当于一个状态。状态机通过内部的 promise 保存当前状态,handle 用于恢复。
3. 编写一个简单的协程
#include <coroutine>
#include <iostream>
#include <optional>
struct Generator {
struct promise_type {
std::optional <int> value;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int v) {
value = v;
return {};
}
Generator get_return_object() {
return Generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> handle;
Generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
bool move_next() {
if (!handle.done()) handle.resume();
return !handle.done();
}
int current_value() const { return *handle.promise().value; }
};
Generator count_to(int n) {
for (int i = 1; i <= n; ++i)
co_yield i;
}
int main() {
auto gen = count_to(5);
while (gen.move_next()) {
std::cout << gen.current_value() << ' ';
}
// 输出: 1 2 3 4 5
}
上述示例演示了一个整数生成器。关键点在于:
yield_value用于产生值并挂起。handle.resume()恢复协程。handle.done()判断协程是否完成。
4. 常见应用场景
4.1 异步 I/O
协程可以与 std::future 或自定义异步库结合,实现无回调、无阻塞的 I/O。例如,使用 asio 与协程配合,代码更直观:
asio::awaitable <void> async_handler(tcp::socket socket) {
char buffer[1024];
std::size_t n = co_await socket.async_read_some(asio::buffer(buffer), asio::use_awaitable);
// 处理读取的数据
}
4.2 生成器模式
协程天然支持懒加载的数据流。可用于实现链式数据处理、流式日志、分页查询等。
4.3 并发调度器
通过自定义 scheduler 与 awaitable,可以实现轻量级任务调度。协程切换开销低,适合高并发场景。
5. 性能与注意事项
- 栈开销:协程使用栈内状态机,局部变量保存在堆或内部缓冲区。过多挂起点可能导致巨大状态表。
- 异常安全:若协程抛出异常,
promise_type::unhandled_exception()将被调用。建议在promise中捕获并转换为可恢复错误。 - 生命周期管理:协程句柄必须在使用后显式销毁。使用
std::experimental::coroutine_handle或std::coroutine_handle的destroy()方法。
6. 进一步学习资源
- 《C++ Concurrency in Action》 – 提供协程与异步编程的实战案例。
- 官方 C++20 标准文档 – 详细描述协程实现细节。
cppreference的协程条目 – 快速参考各类协程相关类型与函数。
通过本文的介绍,读者已掌握 C++20 协程的基本概念、实现原理及典型应用。协程为现代 C++ 带来了更优雅的异步与生成器方案,值得深入学习与实践。