C++20 新增的协程(Coroutines)是一项功能强大的语言特性,它为异步编程提供了更简洁、直观的语法。与传统的回调或 promise/async 方式相比,协程让“暂停”和“恢复”成为一种自然的程序流程。下面我们从概念、语法、实现原理以及实际案例等角度,对协程进行系统阐述,并给出几个实用的小技巧。
1. 协程的基本概念
协程是一种轻量级的函数,能够在执行过程中被“挂起”,随后在某个点继续执行。协程的核心是挂起点(suspend point),它可以是:
co_await:等待异步操作完成。co_yield:产生一个值,挂起并返回给调用者。co_return:结束协程并返回最终结果。
协程的执行状态(上下文)由编译器自动维护,开发者不需要手动管理线程或事件循环。
2. 关键字与语法
co_await:类似于await,等待一个可等待对象。co_yield:用于生成器模式,返回一个值后挂起。co_return:结束协程并返回结果。
#include <coroutine>
#include <iostream>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task async_print() {
std::cout << "Before await\n";
co_await std::suspend_always{}; // 这里挂起,模拟异步等待
std::cout << "After await\n";
}
注意:协程的返回类型是一个可
promise_type结构体的包装类。编译器会根据返回类型自动生成必要的promise_type。
3. 可等待对象(Awaitable)
任何可等待对象必须实现三个成员函数:
await_ready()→bool:是否立即完成。await_suspend(std::coroutine_handle<>)→bool:挂起协程,返回是否挂起。await_resume()→auto:协程恢复后返回值。
struct AsyncTimer {
std::chrono::milliseconds delay;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([h, d=delay]() {
std::this_thread::sleep_for(d);
h.resume(); // 线程结束后恢复协程
}).detach();
}
void await_resume() const noexcept {}
};
这样我们就能用 co_await AsyncTimer{500ms} 实现毫秒级异步等待。
4. 生成器模式
使用 co_yield 可以轻松实现惰性序列。
#include <coroutine>
#include <vector>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T v) {
current_value = v; return {};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Generator get_return_object() { return Generator{ std::coroutine_handle <promise_type>::from_promise(*this) }; }
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle <promise_type> handle;
explicit Generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
struct Iterator {
std::coroutine_handle <promise_type> h;
Iterator(std::coroutine_handle <promise_type> h_) : h(h_) { if (h) h.resume(); }
T operator*() const { return h.promise().current_value; }
Iterator& operator++() { if (h) h.resume(); return *this; }
bool operator!=(const Iterator& other) const { return h != other.h; }
};
Iterator begin() { return Iterator{handle}; }
Iterator end() { return Iterator{nullptr}; }
};
Generator <int> count_to(int n) {
for (int i=1; i<=n; ++i)
co_yield i;
}
使用示例:
for (int x : count_to(5))
std::cout << x << ' '; // 输出 1 2 3 4 5
5. 典型应用场景
| 场景 | 传统实现 | 协程实现 | 优点 |
|---|---|---|---|
| 网络 I/O | 回调 / async / promise |
co_await socket.read() |
代码顺序化,错误处理更直观 |
| 并行流水线 | 线程/Task | co_yield 产生结果流 |
更轻量,资源占用低 |
| 生成惰性序列 | STL generator |
co_yield |
与 STL 迭代器兼容,延迟评估 |
| UI 事件循环 | 信号/槽 | co_await 事件 |
事件驱动更自然,逻辑更清晰 |
6. 小技巧 & 常见坑
-
不要忘记
promise_type的initial_suspend()
默认返回std::suspend_never,意味着协程在调用时即开始执行。若想立即挂起,需要返回std::suspend_always。 -
co_yield与co_await的区别
co_yield产生值并挂起,适合生成器;co_await等待异步结果,挂起后再恢复。 -
线程安全
协程本身是单线程执行的;如果在await_suspend中启动线程,请确保使用std::coroutine_handle::resume()时是线程安全的。 -
异常传播
协程异常会传递到promise_type::unhandled_exception(),默认实现会调用std::terminate()。可自定义抛出自定义异常或记录错误。 -
协程对象的生命周期
协程生成器在销毁时会自动销毁协程状态。若想在外部手动销毁,可调用handle.destroy()。
7. 结语
C++20 协程为现代 C++ 提供了一个统一且强大的异步编程模型。它将传统的异步代码抽象成可读、可维护的同步风格,同时保留了高性能和低开销的优势。掌握协程后,你可以轻松实现高性能网络服务器、异步数据库查询、数据流处理等复杂场景,而不再被回调地狱或事件循环所困扰。
祝你在 C++ 旅程中顺利利用协程,让代码更加简洁、优雅。