在 C++20 中,协程(coroutines)为异步编程提供了语言级支持,极大简化了异步代码的书写。本文将从基本概念入手,展示如何在实际项目中使用协程实现高效、可维护的异步逻辑,并给出常见坑及解决方案。
一、协程基本概念
- 协程:一种可挂起、恢复的函数。
- promise_type:协程的承诺类型,负责管理协程状态。
- generator:最常见的协程形式,用于生成一系列值。
1.1 协程的启动与挂起
std::generator <int> counter(int n) {
for (int i = 0; i < n; ++i) co_yield i; // co_yield:挂起并返回一个值
}
1.2 promise_type 示例
struct counter_promise {
int current{};
auto get_return_object() { return std::generator <int>{*this}; }
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() { return std::suspend_always{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
auto yield_value(int val) {
current = val;
return std::suspend_always{};
}
};
二、协程在 I/O 中的应用
协程可以配合 std::experimental::async 或者自定义 I/O 事件循环实现非阻塞 I/O。
下面以 TCP 服务器为例,展示如何使用协程实现无回调链。
#include <iostream>
#include <boost/asio.hpp>
#include <coroutine>
#include <string>
namespace asio = boost::asio;
using asio::ip::tcp;
class async_read {
public:
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
std::string result;
async_read(handle_type h) : coro(h) {}
async_read(const async_read&) = delete;
async_read(async_read&& rhs) noexcept : coro(rhs.coro) { rhs.coro = nullptr; }
~async_read() { if (coro) coro.destroy(); }
struct promise_type {
std::string value;
std::coroutine_handle<> continuation;
async_read get_return_object() {
return async_read{handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept {
if (continuation) continuation.resume();
return {};
}
void return_value(std::string val) { value = std::move(val); }
void unhandled_exception() { std::terminate(); }
};
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> cont) {
continuation = cont;
}
std::string await_resume() { return std::move(result); }
};
async_read read_from_socket(tcp::socket& sock) {
asio::streambuf buf;
std::size_t n = co_await asio::async_read(sock, buf, asio::use_awaitable);
std::istream is(&buf);
std::string data((std::istreambuf_iterator <char>(is)),
std::istreambuf_iterator <char>());
co_return data;
}
int main() {
asio::io_context io;
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 8080));
tcp::socket sock(io);
acceptor.async_accept(sock, [&](const boost::system::error_code& ec) {
if (!ec) {
asio::co_spawn(io, [&]() -> asio::awaitable <void> {
std::string data = co_await read_from_socket(sock);
std::cout << "Received: " << data << '\n';
}, asio::detached);
}
});
io.run();
}
关键点
asio::use_awaitable让 ASIO 与 C++20 协程无缝协作。async_read用作协程包装器,内部维护继续点continuation。
三、协程与异常处理
协程中异常的传播遵循 promise_type::unhandled_exception 的规则。若需要在协程内部捕获异常,可在 co_try / co_catch 语法中手动处理。
auto task() -> std::generator <int> {
try {
co_yield 1;
throw std::runtime_error("boom");
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << '\n';
co_yield -1;
}
}
四、协程调度器(Scheduler)
为避免协程过多导致资源竞争,可实现一个轻量级调度器,统一管理协程队列。
class Scheduler {
public:
void schedule(std::coroutine_handle<> h) { tasks.emplace_back(h); }
void run() {
while (!tasks.empty()) {
auto h = tasks.front();
tasks.pop_front();
h.resume();
}
}
private:
std::deque<std::coroutine_handle<>> tasks;
};
五、常见坑与解决方案
| 坑 | 原因 | 解决方案 |
|---|---|---|
| 协程泄漏 | coroutine_handle 未被 destroy | 确保 co_return 后手动 coro.destroy() 或使用 RAII 包装 |
| 多线程安全 | std::generator 非线程安全 | 每个线程使用独立协程实例,或使用 std::atomic/锁保护 |
| 堆栈溢出 | 递归协程深度过大 | 将递归改写为循环或使用分层协程 |
六、总结
C++20 协程为异步编程带来了更直观、更接近同步代码的写法。通过与 Boost.Asio 等库结合,可快速构建高性能网络服务。掌握 promise_type、await_suspend、await_resume 三个核心方法,是实现自定义协程的关键。
学习建议
- 从最小的
co_yieldgenerator 开始。- 逐步加入 I/O 事件循环。
- 关注异常传播与资源回收。
祝你在协程之路上越走越顺利!