协程(Coroutines)是 C++20 标准引入的重要特性,它为异步编程、生成器、协作式多任务等提供了语法层面的支持。相比传统的基于回调或线程的异步模型,协程在代码可读性、错误处理以及性能上都有显著优势。本文将从基本概念、语法实现、常见错误以及调试技巧四个方面,帮助你快速上手 C++20 协程。
1. 协程的基本概念
1.1 什么是协程?
协程是一种可以挂起(suspend)和恢复(resume)执行的函数。它们与传统函数不同之处在于:协程可以在执行过程中暂停,随后从暂停点继续,而不是像普通函数那样从头到尾一次性完成。
1.2 为什么需要协程?
- 异步编程:协程让异步代码写起来像同步代码,消除了回调地狱。
- 生成器:使用协程可以轻松实现惰性序列生成。
- 协作式多任务:在单线程内实现多任务切换,减少线程切换开销。
2. 语法实现
C++20 协程通过 co_await、co_yield、co_return 等关键字实现挂起与恢复,并依赖于 协程类型(std::future、generator 等)来控制协程的生命周期。
2.1 基础结构
#include <coroutine>
#include <iostream>
struct coro_handle;
struct generator {
struct promise_type {
int current_value;
std::suspend_always yield_value(int v) {
current_value = v;
return {};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
generator get_return_object() {
return {std::coroutine_handle <promise_type>::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> coro;
explicit generator(std::coroutine_handle <promise_type> h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
struct iterator {
std::coroutine_handle <promise_type> coro;
iterator(std::coroutine_handle <promise_type> h) : coro(h) {}
int operator*() const { return coro.promise().current_value; }
iterator& operator++() {
coro.resume();
return *this;
}
bool operator!=(const iterator& other) const { return coro != other.coro; }
};
iterator begin() { coro.resume(); return iterator{coro}; }
iterator end() { return iterator{nullptr}; }
};
2.2 生成器示例
generator fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
2.3 异步任务示例
#include <future>
std::future <int> async_add(int a, int b) {
co_return a + b;
}
3. 常见错误与调试技巧
3.1 co_await 只能在可 await 的类型上使用
int main() {
int x = 5;
co_await x; // ❌ compile error
}
解决方案:使用
std::suspend_always或std::suspend_never包装对象。
3.2 协程函数返回类型不匹配
generator foo() {
co_return 42; // ❌ error: cannot return int from generator
}
解决方案:
co_return必须与promise_type的返回类型匹配。
3.3 未处理的异常导致程序终止
协程内部抛出的异常如果未在 unhandled_exception() 中处理,将触发 std::terminate()。
解决方案:在
promise_type::unhandled_exception()中使用std::current_exception()或记录日志。
3.4 调试协程
- 使用 GDB 或 LLDB:
bt可以查看堆栈,但因为协程拆分函数,可能看不到完整的调用链。使用info coroutine(GDB)可以列出所有协程实例。 - 自定义
promise_type:在yield_value或initial_suspend、final_suspend中加入std::cout打印调试信息。 - 可视化工具:Clang 自带的
-fcoroutines选项可生成中间文件,配合llvm-cov观察协程执行路径。
4. 性能考量
- 堆栈使用:协程的协程帧在堆上分配,避免了线程堆栈的限制,但需要注意堆栈大小与对象数量。
- 上下文切换开销:协程切换比线程切换更轻量,但过多的挂起/恢复会带来函数调用开销。
- 异步 I/O:与事件循环配合使用可显著降低系统资源占用。
5. 小结
- C++20 协程通过
co_await/co_yield/co_return让异步、生成器代码写起来像同步代码。 - 协程类型(
promise_type)决定了协程的生命周期、挂起行为和返回值。 - 常见错误主要来自类型不匹配、异常未处理以及
co_await使用错误。 - 调试时可以利用 GDB 的 coroutine 相关命令,或在
promise_type中加入日志。 - 性能方面协程比线程更轻量,但需注意堆栈分配和上下文切换频率。
通过掌握上述基本概念与技巧,你就能在项目中自如地使用 C++20 协程,实现高效、可维护的异步逻辑。祝编码愉快!