C++20 引入了协程(coroutines)这一强大的语言特性,为异步编程、生成器、延迟计算等场景提供了高效而简洁的实现方式。本文将从协程的基本概念、语法实现、编译器支持以及实际应用案例四个方面,系统梳理 C++20 协程的关键要点,并展示如何在现代项目中发挥协程的价值。
1. 协程概念回顾
协程是一种可挂起、可恢复的函数,能够在执行过程中暂停(yield)并在后续恢复,保持其内部状态。与传统线程相比,协程的切换成本极低(仅需保存/恢复栈帧指针等少量状态),适合大量并发任务的轻量级处理。C++20 通过对 co_await、co_yield、co_return 三个关键字的引入,构成了协程的基本语法框架。
2. 关键字与语法细节
co_await:用于等待一个 awaitable 对象(如 future、promise 等)完成。co_yield:在生成器中返回一个值,同时将协程挂起,等待下一个co_await或resume。co_return:结束协程,返回最终结果。
#include <coroutine>
#include <iostream>
struct generator {
struct promise_type {
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 {}; }
void return_void() {}
generator get_return_object() { return {this}; }
};
promise_type* promise_;
generator(promise_type* p) : promise_(p) {}
struct iterator {
generator* gen_;
bool operator++() { return gen_->promise_->initial_suspend().await_suspend(gen_->promise_); }
int operator*() const { return gen_->promise_->value_; }
};
iterator begin() { return {this}; }
void end() {}
};
generator range(int n) {
for (int i = 0; i < n; ++i)
co_yield i;
}
上述代码演示了一个简单的整数生成器,使用 co_yield 在每次迭代返回一个值。
3. Awaitable 对象设计
协程只能挂起 awaitable 对象。一个类型满足 awaitable 的条件是实现 await_ready、await_suspend 和 await_resume 三个成员函数。
struct timer {
std::chrono::milliseconds dur_;
bool await_ready() const noexcept { return dur_.count() == 0; }
void await_suspend(std::coroutine_handle<> h) const {
std::async(std::launch::async, [h, d = dur_]() {
std::this_thread::sleep_for(d);
h.resume();
});
}
void await_resume() const noexcept {}
};
在上面的 timer 对象中,协程通过 co_await timer(1000ms) 实现毫秒级延迟。
4. 编译器与标准库支持
- GCC 10+ / Clang 11+ / MSVC 19.26+:已完整实现协程基础。
- **` `**:包含了 `std::coroutine_handle`、`std::suspend_always`、`std::suspend_never` 等工具。
<experimental/coroutine>:早期实现,已在标准库中合并。
在实际项目中,推荐使用 std::experimental::generator(C++23 预览)来简化生成器实现。
5. 实际应用场景
5.1 轻量级异步 IO
协程与 io_context、asio 等库结合,可以写出更直观的异步代码。
asio::awaitable <void> async_read(asio::ip::tcp::socket& sock) {
char buf[1024];
std::size_t n = co_await sock.async_read_some(asio::buffer(buf), asio::use_awaitable);
std::cout << "Read " << n << " bytes\n";
}
5.2 并发任务调度
利用协程生成器实现工作池,避免线程上下文切换。
generator task_pool {
for (auto& task : tasks)
co_yield std::move(task);
}
5.3 实时游戏循环
在游戏引擎中,协程可用于实现 AI 行为树、动画序列等,保持代码可读性同时提升性能。
6. 性能与安全注意
- 栈帧存储:协程默认将局部变量压入堆栈(或堆中)以保持挂起状态。
- 异常传播:协程内部抛出的异常会被包装为
std::exception_ptr并由await_resume重新抛出。 - 资源管理:使用
std::unique_ptr或自定义 RAII 包装器,确保协程挂起时资源安全。
7. 小结
C++20 协程为开发者提供了一种既高效又易用的异步编程模型。通过掌握 co_await、co_yield 与 awaitable 的实现细节,结合标准库与第三方库的支持,能够在多种领域(网络编程、并发计算、游戏开发)中写出简洁、可维护且性能卓越的代码。随着 C++23 的到来,协程相关特性将进一步完善,期待更多生态工具为协程编程提供便利。