在C++20中,协程(coroutine)为异步编程提供了语言级支持。co_await、co_yield和co_return让我们能够像编写同步代码一样写出异步逻辑。本文将演示如何在网络I/O中使用协程,并解释背后的关键概念。
1. 基本概念
- promise_type:每个协程都有一个关联的
promise_type,它负责创建协程句柄、存储返回值、处理异常等。 - awaitable:实现
await_ready(),await_suspend(),await_resume()接口的对象,表示可以被co_await的对象。 - co_await:在执行到
co_await时,协程会挂起,直到awaitable完成。
2. 简单示例:文件读取
假设我们有一个异步文件读取接口async_read_file,返回一个awaitable<std::string>。下面是一个使用协程读取文件并打印内容的例子:
#include <iostream>
#include <string>
#include <future>
#include <thread>
#include <chrono>
struct AwaitableRead {
std::string file_path;
std::promise<std::string> prom;
AwaitableRead(const std::string& path) : file_path(path) {}
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 异步读取模拟:在后台线程读取文件
std::thread([this, h]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟延迟
// 这里可以放置真正的文件读取逻辑
prom.set_value("文件内容:" + file_path);
h.resume(); // 恢复协程
}).detach();
}
std::string await_resume() { return prom.get_future().get(); }
};
AwaitableRead async_read_file(const std::string& path) {
return AwaitableRead(path);
}
auto read_and_print() -> std::future <void> {
std::string content = co_await async_read_file("example.txt");
std::cout << content << std::endl;
}
3. 处理异常
如果读取过程中抛出异常,可以在await_suspend里捕获并传递给promise:
void await_suspend(std::coroutine_handle<> h) {
std::thread([this, h]() {
try {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 假设读取失败
throw std::runtime_error("读取错误");
prom.set_value("...");
} catch (...) {
prom.set_exception(std::current_exception());
}
h.resume();
}).detach();
}
4. 与std::future的整合
C++20标准库提供std::future与协程的交互方式。我们可以用co_return返回值给std::future:
auto async_operation() -> std::future <int> {
co_return 42; // 直接返回给future
}
5. 性能考虑
- 协程的状态机生成开销很小,通常不超过几十字节。
await_suspend与await_resume的调用链会产生函数调用开销,但相较于传统回调式,简洁且易于维护。- 需要注意:如果在协程中使用阻塞操作,会导致线程阻塞,失去协程非阻塞的优势。
6. 总结
C++20协程通过co_await简化了异步代码的书写,使得异步逻辑与同步代码保持一致。通过自定义awaitable对象,可以将任何异步操作(如网络、文件、定时器)包装成协程接口。掌握这些核心概念后,你就可以在现代C++项目中轻松实现高性能、可维护的异步系统。
祝你编码愉快,C++协程之旅顺利开启!