协程(coroutine)是 C++20 开始正式加入标准的一项强大功能,它为异步编程和生成器提供了一种简洁、可组合的语法。在 C++23 中,协程的实现进一步成熟,提供了更高效的状态机生成、可移植的调度器支持以及与标准库容器的深度集成。本文将从协程的概念入手,介绍其关键语法、典型使用场景以及在 C++23 中的新特性,并给出一段完整的演示代码,帮助你快速上手。
1. 协程的核心概念
| 术语 | 说明 |
|---|---|
| 协程函数 | 带 co_await, co_yield, co_return 的函数,返回类型为 std::experimental::generator, std::future, std::task 等 |
| 挂起点 | co_await, co_yield, co_return 触发协程暂停并返回控制权 |
| 恢复点 | 当外部再次请求协程时,从挂起点继续执行 |
| 协程句柄 | std::experimental::coroutine_handle,用于控制协程的生命周期(resume, destroy 等) |
| 协程 Promise | 每个协程都有一个 Promise 对象,用于携带返回值、异常以及协程状态 |
注意:协程在 C++23 中仍属于实验性特性,官方通过
<experimental/coroutine>或<coroutine>实现;在实际项目中应使用对应的实现库(如libcoro或编译器自带实现)以获得稳定性。
2. 基本语法
#include <coroutine>
#include <iostream>
#include <vector>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task hello() {
std::cout << "Hello, ";
co_await std::suspend_always{};
std::cout << "world!\n";
}
promise_type定义了协程的行为:initial_suspend与final_suspend决定协程启动和结束时是否挂起。co_await用于挂起并等待异步结果;co_yield用于生成器返回值;co_return用于结束并返回值。
3. C++23 新特性
| 新特性 | 说明 |
|---|---|
| std::generator | 直接支持生成器语法,无需手动实现 Promise |
| std::task | 轻量级异步任务,兼容 std::future 和 std::shared_future |
| 协程调度器 | std::experimental::schedulers 提供基于事件循环的调度器实现 |
co_await 的异构支持 |
可以对任何实现 await_transform 的类型进行挂起 |
| 协程的复制 | 通过 std::experimental::coroutine_handle::move 实现协程句柄的移动语义 |
这些改进使得协程的使用更加自然,减少模板魔法,并为高并发编程提供了更好的性能。
4. 示例:协程 + 线程池 + HTTP 请求
下面的代码演示如何使用 C++23 的协程与线程池实现一个简单的异步 HTTP 客户端。我们使用 asio(Boost.Asio 或 standalone Asio)来处理网络 IO,并用协程包装异步操作。
#include <asio.hpp>
#include <iostream>
#include <coroutine>
#include <thread>
#include <vector>
using asio::ip::tcp;
// 简单的协程包装器
struct Awaitable {
asio::ip::tcp::socket& socket;
std::size_t size;
char* data;
asio::error_code ec = asio::error::make_error_code(asio::error::operation_aborted);
struct promise_type {
Awaitable get_return_object() {
return {nullptr, 0, nullptr};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::rethrow_exception(std::current_exception()); }
};
Awaitable operator co_await() const noexcept {
return *this;
}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
socket.async_read_some(
asio::buffer(data, size),
[h](asio::error_code ec, std::size_t /*bytes*/) mutable {
if (ec) {
std::cerr << "Read error: " << ec.message() << '\n';
}
h.resume();
});
}
void await_resume() {}
};
// 异步 HTTP GET
asio::awaitable<std::string> async_get(asio::io_context& io, const std::string& host, const std::string& path) {
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
auto endpoints = co_await resolver.async_resolve(host, "http", asio::use_awaitable);
tcp::socket socket(executor);
co_await asio::async_connect(socket, endpoints, asio::use_awaitable);
std::string request = "GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n";
co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable);
std::string response;
char buf[1024];
for (;;) {
std::size_t n = co_await socket.async_read_some(asio::buffer(buf), asio::use_awaitable);
if (n == 0) break;
response.append(buf, n);
}
co_return response;
}
// 线程池
struct ThreadPool {
asio::io_context io;
std::vector<std::thread> workers;
ThreadPool(std::size_t n = std::thread::hardware_concurrency()) {
workers.reserve(n);
for (std::size_t i = 0; i < n; ++i)
workers.emplace_back([this] { io.run(); });
}
~ThreadPool() { io.stop(); for (auto& t : workers) t.join(); }
};
int main() {
ThreadPool pool;
auto [response] = asio::co_spawn(pool.io, async_get(pool.io, "example.com", "/"), asio::detached);
// 此时协程已在线程池中执行,主线程可继续其他工作
std::cout << "Fetched " << response.size() << " bytes\n";
return 0;
}
说明
- 线程池:使用
asio::io_context的run()在多个线程中调度协程。 - 异步
async_get:使用asio::awaitable作为返回类型,内部调用async_*操作并通过co_await让协程挂起。 co_spawn:启动协程并将其绑定到io_context,asio::detached表示不等待结果(可改为use_future获取std::future)。
5. 常见坑 & 性能优化
| 问题 | 解决方案 |
|---|---|
| 协程句柄泄漏 | 明确在 final_suspend 后调用 destroy(),或者使用 std::suspend_always 并在 return_object() 中返回句柄。 |
| 异常传播 | promise_type::unhandled_exception 默认重新抛出,若想自定义可在此处记录日志。 |
| 堆栈开销 | 协程状态机在堆上分配,若频繁创建请使用 co_yield 生成器或 std::generator。 |
| 线程安全 | 多线程访问共享数据时仍需同步;协程仅保证挂起点与恢复点的顺序。 |
| 调试难度 | 使用 -fsanitize=address -fsanitize=undefined 或 -fdiagnostics-color=always 可以帮助定位问题。 |
6. 小结
- C++23 的协程让异步代码写得更像同步代码,极大提升可读性与可维护性。
- 与
asio等异步库配合使用,可实现高性能网络应用、游戏循环或 GUI 事件驱动。 - 需要注意的是协程本身是轻量级的,但真正的 IO 仍由底层事件循环或线程池完成。
通过掌握协程的基本语法与 C++23 的新特性,你可以在自己的项目中快速引入高并发、低延迟的异步机制,从而提升整体性能与开发效率。祝你编码愉快!