协程是 C++20 引入的一项强大特性,它让异步编程变得像同步编程一样直观。下面我们通过一个简单的网络请求示例,演示如何使用协程实现异步 IO,并讨论常见的陷阱与最佳实践。
1. 协程基础
协程通过 co_yield、co_return、co_await 等关键字实现“暂停”和“恢复”功能。协程函数的返回类型必须是 std::experimental::generator、**`std::experimental::generator
`** 或 **`std::experimental::task`**(后者在标准 C++20 中是 `std::future` 的一个包装)。
“`cpp
#include
#include
#include
#include
std::future async_http_get(const std::string& url);
“`
### 2. 异步 HTTP GET 的协程实现
下面的示例演示如何用协程包装 `std::async`,并在等待期间让线程池或事件循环继续执行其它任务。
“`cpp
#include
#include
#include
#include
#include
#include
// 模拟网络请求的耗时操作
std::string fake_http_get(const std::string& url)
{
std::this_thread::sleep_for(std::chrono::seconds(2));
return “Response from ” + url;
}
// 把耗时操作包装成协程
std::future async_http_get(const std::string& url)
{
// 这里用 std::async 来模拟异步操作,真实项目可替换成 ASIO 等库
return std::async(std::launch::async, fake_http_get, url);
}
// 协程入口
std::future
process_requests(const std::vector& urls)
{
for (const auto& url : urls)
{
// 发起异步请求
std::future fut = async_http_get(url);
// 这里可以做其他工作,例如更新 UI
std::cout << "发起请求: " << url << std::endl;
// 等待结果
std::string response = co_await fut; // C++20 协程语法
// 处理结果
std::cout << "收到响应: " << response < **注意**:C++20 标准库并没有直接提供 `co_await` 的实现,`std::future` 本身不支持协程。实际使用时应依赖 **`std::experimental::task
`** 或第三方库(如 `cppcoro`、`libcoro`、`asio` 的协程适配器)。上例为伪代码,展示思路。
### 3. 事件循环与协程
在实际项目中,协程往往与事件循环(Event Loop)配合使用。下面是一个简化的事件循环示例,展示如何将协程与 `select`/`epoll` 等 IO 复用机制整合。
“`cpp
#include
#include
#include
class EventLoop {
public:
EventLoop() { epfd_ = epoll_create1(0); }
~EventLoop() { close(epfd_); }
void add_fd(int fd, int events)
{
epoll_event ev{};
ev.events = events;
ev.data.fd = fd;
epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
}
void run()
{
while (true) {
std::array events;
int nfds = epoll_wait(epfd_, events.data(), events.size(), -1);
for (int i = 0; i < nfds; ++i) {
// 根据事件类型唤醒相应的协程
handle_event(events[i].data.fd, events[i].events);
}
}
}
private:
int epfd_;
void handle_event(int fd, int events) { /* … */ }
};
“`
### 4. 常见陷阱
| 陷阱 | 说明 | 对策 |
|——|——|——|
| **协程对象生命周期** | 协程返回值持有协程状态,若返回值被提前销毁会导致悬挂。 | 确保返回值存活,或使用 `std::shared_ptr` 包装。 |
| **`co_await` 与 `std::future` 不兼容** | `std::future` 无 `await_ready` 等成员。 | 使用 `std::experimental::task` 或第三方库。 |
| **错误传播** | 协程内部抛出的异常默认会通过返回的 `std::future` 抛出。 | 使用 `try/catch` 捕获,或通过 `std::expected` 等包装。 |
| **性能开销** | 每个协程需要堆栈空间,过多协程会导致堆栈碎片。 | 采用协程池或轻量协程实现。 |
### 5. 小结
C++20 协程为异步 IO 提供了直观、可组合的语法糖。通过协程与事件循环、任务调度器的配合,可以写出既易读又高性能的异步代码。虽然标准库尚未完全提供协程友好的异步组件,但现有的第三方库已足以满足大多数需求。未来的 C++ 可能会在标准库中加入更完善的协程支持,让异步编程更进一步。