在C++20中,协程(coroutine)提供了一种轻量级的、可暂停的函数机制,使得异步编程和生成器模式得以简洁实现。本文将从协程的基础概念、核心关键词、实现步骤以及典型应用场景展开,帮助你快速上手并在项目中灵活运用。
1. 协程的核心概念
| 关键词 | 说明 |
|---|---|
co_await |
暂停协程,等待异步操作完成后恢复 |
co_yield |
暂停协程,返回一个值给调用者 |
co_return |
结束协程,返回最终结果 |
std::suspend_always / std::suspend_never |
控制协程暂停行为的策略 |
std::coroutine_handle |
对协程内部状态的句柄,负责手动管理生命周期 |
协程本质上是一段被拆分为若干状态机的函数,编译器根据 co_* 关键字生成状态机的实现。调用者与协程之间通过句柄(handle)进行交互,决定何时恢复执行。
2. 基本示例:异步文件读取
#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
#include <filesystem>
struct AsyncFileReader {
struct promise_type {
std::string result;
AsyncFileReader get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
AsyncFileReader read_file(const std::string& path) {
std::ifstream file(path);
if (!file) {
std::cerr << "Cannot open file: " << path << '\n';
co_return;
}
std::string line;
while (std::getline(file, line)) {
co_yield line; // 每行返回一次
}
}
int main() {
auto reader = read_file("example.txt");
auto handle = std::coroutine_handle<AsyncFileReader::promise_type>::from_promise(reader);
while (!handle.done()) {
handle.resume(); // 恢复协程
}
}
说明
该示例展示了如何使用协程实现文件行的逐行读取。co_yield用于返回每一行内容,主循环通过handle.resume()逐步获取下一行。
3. 生成器模式的典型实现
协程是实现生成器的天然工具,下面给出一个生成斐波那契数列的协程:
#include <iostream>
#include <coroutine>
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
Generator(handle_type h) : coro(h) {}
~Generator() { if (coro) coro.destroy(); }
T next() {
coro.resume();
return coro.done() ? T{} : coro.promise().current_value;
}
struct promise_type {
T current_value;
Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Generator <int> fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int tmp = a + b;
a = b;
b = tmp;
}
}
int main() {
for (int i = 0; i < 10; ++i) {
std::cout << fib(10).next() << ' ';
}
}
4. 协程与异步 IO
C++20 的协程配合 std::experimental::net(或 Boost.Asio 等库)可以实现高效的异步网络程序。核心思路是:
-
异步操作返回协程类型
`,内部实现为 `std::coroutine_handle` 与 IO 库的回调机制结合。
如 `awaitable -
co_await与 I/O 事件
在协程中使用co_await使协程挂起,等待事件触发后再恢复。
示例:使用 Boost.Asio 的 co_spawn:
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable.hpp>
boost::asio::awaitable <void> async_echo(boost::asio::ip::tcp::socket socket) {
char data[1024];
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
co_await boost::asio::async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
}
int main() {
boost::asio::io_context io_context;
boost::asio::ip::tcp::acceptor acceptor(io_context, {boost::asio::ip::tcp::v4(), 12345});
for (;;) {
auto socket = co_spawn(io_context, acceptor.async_accept(), boost::asio::use_awaitable);
co_spawn(io_context, async_echo(std::move(socket)), boost::asio::detached);
}
}
5. 常见陷阱与调试技巧
| 陷阱 | 解决方案 |
|---|---|
| 协程句柄误销毁 | 确认 handle.done() 前不要手动销毁句柄 |
| 内存泄漏 | 协程内使用裸指针时,确保在 final_suspend 里清理 |
| 性能问题 | 过度使用 co_yield 产生大量状态切换,尽量在合适粒度下使用 |
调试协程可通过输出协程状态、使用 std::coroutine_handle::promise() 打印内部数据,或使用现代 IDE 的协程调试插件。
6. 结语
C++20 协程为 C++ 开发者提供了一种更直观、资源友好的方式来处理异步与生成器任务。掌握 co_await、co_yield 与 co_return 的使用,以及协程句柄生命周期管理,是在项目中高效使用协程的关键。随着标准库与第三方库对协程支持的完善,未来协程将成为 C++ 开发中不可或缺的一环。祝你在协程的世界里玩得开心!