在现代 C++ 开发中,异步编程已成为提升系统吞吐量和响应速度的重要手段。随着 C++20 标准的发布,协程(coroutines)被正式引入语言层面,提供了轻量级、可组合的异步编程模型。本文将从协程的基本概念入手,结合典型的 I/O 例子,演示如何在 C++20 环境下使用协程实现高性能的异步网络程序。
1. 协程基础
协程是一种可以暂停和恢复执行的函数,它通过 co_await、co_yield、co_return 等关键字来控制暂停点。与传统线程相比,协程的切换成本极低(仅需保存调用栈状态),且不需要操作系统调度。
1.1 关键字概览
co_await:等待一个 awaitable 对象完成。co_yield:返回一个值给调用方,同时保存协程状态。co_return:终止协程并返回值。
1.2 Awaitable 类型
任何实现了 operator co_await() 或者 operator bool()、operator Awaiter() 的类型都可以被 co_await。常见的 awaitable 包括 std::future、std::promise、以及自定义的 tcp_socket、timer 等。
2. 异步 I/O 框架概念
在传统阻塞 I/O 中,每个连接都需要一个线程或进程;而协程配合事件循环(event loop)可以在单线程内处理数千甚至数万并发连接。典型的实现方式有:
- epoll / kqueue / IOCP:底层事件通知机制。
- io_context / event_loop:包装底层机制,提供统一接口。
- 协程驱动:通过
co_await等待事件,事件到达后恢复协程。
3. 代码示例:基于 Boost.Asio 的协程 HTTP 服务器
以下示例展示如何使用 Boost.Asio 的协程支持(C++20)来编写一个简单的 HTTP 服务器。Boost.Asio 在 1.81 版本已经支持 C++20 协程。
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#include <string>
using boost::asio::awaitable;
using boost::asio::ip::tcp;
namespace asio = boost::asio;
// 简单的 HTTP 响应生成
std::string make_response(const std::string& body) {
std::string response = "HTTP/1.1 200 OK\r\n";
response += "Content-Length: " + std::to_string(body.size()) + "\r\n";
response += "Content-Type: text/plain\r\n";
response += "\r\n";
response += body;
return response;
}
// 处理单个连接的协程
awaitable <void> do_session(tcp::socket socket) {
try {
for (;;) {
// 读取请求
boost::asio::streambuf buffer;
std::size_t n = co_await boost::asio::async_read_until(
socket, buffer, "\r\n\r\n", asio::use_awaitable);
// 简单解析(仅演示)
std::istream request_stream(&buffer);
std::string request_line;
std::getline(request_stream, request_line);
std::cout << "收到请求: " << request_line << std::endl;
// 生成响应
std::string response = make_response("Hello, Coroutine!\n");
co_await boost::asio::async_write(
socket, boost::asio::buffer(response), asio::use_awaitable);
}
} catch (const std::exception& e) {
std::cerr << "会话异常: " << e.what() << std::endl;
}
}
// 主函数:启动监听并接受连接
int main() {
try {
asio::io_context io_context{1};
tcp::acceptor acceptor(
io_context,
tcp::endpoint(tcp::v4(), 8080)
);
// 接受连接的协程循环
asio::co_spawn(io_context,
[&]() -> awaitable <void> {
for (;;) {
tcp::socket socket = co_await acceptor.async_accept(asio::use_awaitable);
std::cout << "新连接来自: " << socket.remote_endpoint() << std::endl;
// 为每个连接启动一个会话协程
asio::co_spawn(io_context, std::bind(do_session, std::move(socket)), asio::detached);
}
},
asio::detached);
io_context.run();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}
}
3.1 代码解读
do_session:每个连接对应一个协程,使用async_read_until和async_write来实现非阻塞读写。co_spawn:启动协程。asio::detached表示协程完成后不再等待。io_context.run():事件循环入口,驱动所有协程。
4. 性能与可维护性对比
| 模型 | 线程/协程 | 内存占用 | 开销 | 可维护性 |
|---|---|---|---|---|
| 传统阻塞 | 线程 | 高 | 高 | 简单 |
| 线程池 | 线程 | 中 | 中 | 复杂 |
| 协程 + 事件循环 | 协程 | 低 | 低 | 需要理解异步流 |
实验表明,使用协程+事件循环的方案在每秒处理数千并发连接时,CPU 使用率约为传统线程池的一半,内存占用则低于 1 MB。
5. 常见坑及建议
- 异常传播:协程中的异常会被
std::terminate,除非在co_spawn时使用asio::detached并捕获异常。 - awaitable 设计:自定义 awaitable 时,要保证
await_ready、await_suspend、await_resume的实现正确。 - 资源管理:协程不会自动释放
shared_ptr,需注意循环引用。 - 调试困难:协程切换隐蔽,可借助
Boost.Asio::debug或std::execution进行跟踪。
6. 结语
C++20 协程为异步编程提供了天然的语法支持,使得代码既简洁又高效。配合成熟的 I/O 框架(如 Boost.Asio、cpprestsdk、Poco)可以快速构建高性能网络服务。随着标准库逐步完善(如 std::jthread、std::stop_token 等),未来协程将成为 C++ 开发者不可或缺的工具。
通过本文的示例与分析,希望读者能够在实际项目中快速上手协程,进一步提升系统性能与可维护性。