协程(Coroutines)是 C++20 标准中一项重要的新特性,它为异步编程、协作式多任务提供了一种更简洁、可读性更高的语法。与传统的回调或 Promise 方式相比,协程可以让代码像同步那样书写,却在底层实现了异步执行。本文将从协程的基本概念、关键语法、实现细节以及常见应用场景等方面进行阐述,并给出完整的代码示例,帮助读者快速上手。
1. 协程的核心概念
- 协程函数:标记为
co_await、co_yield或co_return的函数。它可以在执行过程中挂起(suspend)并在需要时恢复。 - promise_type:每个协程函数都关联一个
promise_type,用于管理协程的生命周期、返回值、异常以及挂起点。 std::coroutine_handle:指向协程状态的句柄,通过它可以控制协程的执行(resume、destroy 等)。- 悬挂点:
co_await、co_yield、co_return等关键字出现的位置称为悬挂点,决定了协程何时挂起。
2. 关键语法
#include <coroutine>
#include <iostream>
#include <string>
2.1 协程函数返回类型
C++20 允许协程返回一个拥有 promise_type 的类型,最常见的是 std::future、std::generator 等。我们可以自定义一个简单的 Task 类型来演示:
struct Task {
struct promise_type {
int value_;
Task get_return_object() { return Task{std::coroutine_handle <promise_type>::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_value(int v) { value_ = v; }
};
std::coroutine_handle <promise_type> h_;
int get() { return h_.promise().value_; }
~Task() { if (h_) h_.destroy(); }
};
2.2 关键字使用
co_await expr:等待expr的结果,挂起当前协程。co_yield expr:在生成器中产生一个值,挂起当前协程。co_return expr:结束协程并返回expr。
3. 示例:异步文件读取
下面演示一个简化的异步文件读取协程。实际项目中可以结合 std::filesystem、asio 或 boost::asio 等 IO 库。
#include <coroutine>
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
struct AsyncReadResult {
std::vector <char> buffer;
};
struct AsyncReadTask {
struct promise_type {
AsyncReadResult result_;
AsyncReadTask get_return_object() {
return AsyncReadTask{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_value(AsyncReadResult&& res) { result_ = std::move(res); }
};
std::coroutine_handle <promise_type> h_;
AsyncReadResult get() { return std::move(h_.promise().result_); }
~AsyncReadTask() { if (h_) h_.destroy(); }
};
AsyncReadTask async_read_file(const std::string& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
throw std::runtime_error("文件打开失败");
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector <char> buffer(static_cast<size_t>(size));
if (!file.read(buffer.data(), size)) {
throw std::runtime_error("读取失败");
}
// 模拟异步挂起
co_await std::suspend_always{};
AsyncReadResult res{std::move(buffer)};
co_return std::move(res);
}
int main() {
try {
AsyncReadTask task = async_read_file("example.txt");
// 在这里可以执行其他工作
AsyncReadResult result = task.get();
std::cout << "文件大小: " << result.buffer.size() << " 字节\n";
} catch (const std::exception& ex) {
std::cerr << "异常: " << ex.what() << std::endl;
}
return 0;
}
3.1 说明
async_read_file通过co_await std::suspend_always模拟一次挂起,实际异步场景会在 IO 完成后再恢复。AsyncReadTask的promise_type中return_value用来传递读取结果。
4. 常见 pitfalls 与调试建议
- 忘记
co_return:协程没有返回值时,co_return可写作co_return;。若遗漏,编译器会报错。 - 异常泄露:如果协程内部抛出异常,
promise_type::unhandled_exception需要妥善处理,否则会导致程序终止。 - 资源泄露:
std::coroutine_handle需要手动销毁,建议使用 RAII 包装。 - 性能开销:协程在内部会创建堆上对象(如 promise),过度使用会产生 GC 噪音。适当使用
std::suspend_always与std::suspend_never控制挂起点。
5. 典型应用场景
| 场景 | 说明 |
|---|---|
| 异步网络 IO | 与 asio、libuv 等事件循环结合,实现高并发网络服务器。 |
| 并行计算 | 使用 co_await 配合 std::async 或 std::thread,实现任务拆分与协作。 |
| 生成器 | 通过 co_yield 生成无限序列(如斐波那契数列)。 |
| GUI 事件驱动 | 在 UI 主线程与后台线程之间同步数据,避免阻塞。 |
6. 进一步阅读
- C++20 标准草案 – 章节 29.7 “协程”。
- 《C++协程实战》 – 详细介绍协程的设计与应用。
- 官方库
cppcoro– 提供高层次协程封装。
结语
协程为 C++ 提供了统一、强大的异步编程模型,降低了回调地狱与 Promise 链式调用的复杂度。掌握基本语法、了解 promise 机制并结合实际项目场景进行练习,是快速成为协程高手的关键。祝你编码愉快!