在 C++20 标准中,协程(Coroutines)被正式纳入语言层面,为异步编程提供了更直观、更高效的方式。本文将从协程的基本概念、语法细节、实现机制以及实际应用场景进行系统阐述,并给出完整代码示例。
1. 协程的基本概念
协程是一种轻量级的“挂起”和“恢复”函数。与线程不同,协程在同一线程中运行,切换成本极低,适合需要频繁切换执行状态的场景,例如网络 I/O、游戏循环、动画渲染等。
C++20 协程的核心在于 co_await、co_yield、co_return 三个关键字,以及 generator、task 等模板封装。协程的生命周期由awaiter对象管理,awaiter 定义了何时挂起、何时恢复、何时结束。
2. 语法细节
2.1 基本协程函数
#include <coroutine>
#include <iostream>
struct Generator {
struct promise_type {
int current_value;
std::suspend_always yield_value(int value) {
current_value = value;
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::terminate(); }
};
std::coroutine_handle <promise_type> handle;
explicit Generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
bool next() {
if (!handle.done()) handle.resume();
return !handle.done();
}
int value() const { return handle.promise().current_value; }
};
Generator range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
2.2 Task(异步函数)
#include <coroutine>
#include <iostream>
#include <future>
template<typename T>
struct Task {
struct promise_type {
T value;
std::exception_ptr eptr;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Task get_return_object() {
return Task{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void return_value(T v) { value = v; }
void unhandled_exception() { eptr = std::current_exception(); }
};
std::coroutine_handle <promise_type> handle;
explicit Task(std::coroutine_handle <promise_type> h) : handle(h) {}
~Task() { if (handle) handle.destroy(); }
T get() {
if (handle.promise().eptr) std::rethrow_exception(handle.promise().eptr);
return handle.promise().value;
}
};
Task <int> async_add(int a, int b) {
co_return a + b;
}
3. 协程的实现机制
C++20 协程本质上是对 state machine 的编译器支持。每个协程被编译成:
- promise_type:封装协程的状态、返回值、异常。
- handle:指向 promise 的 coroutine handle。
- awaiter:实现
co_await、co_yield的对象,决定挂起、恢复逻辑。
编译器负责把 co_yield 生成 yield_value 调用,将 co_return 生成 return_value 调用,并在生成器中插入 initial_suspend 与 final_suspend 逻辑。
协程在 栈上 运行,只有 promise 对象在堆上。挂起时,调用者可以通过 std::coroutine_handle 控制恢复,保证切换成本几乎等同于普通函数调用。
4. 实际应用场景
4.1 异步 I/O
在网络编程中,使用协程可避免回调地狱。示例代码:
#include <asio.hpp>
#include <iostream>
asio::awaitable <void> read_socket(asio::ip::tcp::socket& sock) {
std::array<char, 1024> buffer;
while (true) {
std::size_t n = co_await sock.async_read_some(asio::buffer(buffer), asio::use_awaitable);
std::cout << "Received: " << std::string(buffer.data(), n) << '\n';
}
}
4.2 GUI 事件循环
GUI 框架可以把 UI 更新包装成协程,使事件处理逻辑更直观。
async void animate() {
for (int frame = 0; frame < 60; ++frame) {
update_frame(frame);
co_await std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}
4.3 游戏引擎
协程用于实现脚本行为、状态机、路径规划等。其低切换成本使游戏逻辑更易维护。
5. 性能与注意事项
- 挂起成本:协程切换成本低于线程切换,但仍比普通函数略高。应避免频繁
co_yield在极高性能路径。 - 资源管理:promise 位于堆上,需手动销毁或使用
std::unique_ptr。编译器会在final_suspend之后自动销毁。 - 异常传播:协程异常需通过
promise_type::unhandled_exception捕获,并在get()时rethrow_exception。
6. 结语
C++20 协程为语言带来了异步编程的新语义,既保持了 C++ 的高性能特性,又提供了更直观的控制流模型。随着标准化和库的成熟,协程将在网络、图形、游戏、嵌入式等领域发挥越来越重要的作用。希望本文能帮助你快速上手协程,并在项目中灵活运用。