协程(Coroutine)在 C++20 里正式加入语言核心,C++23 进一步完善了其标准库支持,尤其是对异步 I/O 的统一处理方式。本文将从协程的基本概念、实现原理以及在现代 C++ 项目中的典型使用场景入手,帮助读者快速上手并发挥协程的最大价值。
1. 协程概述
协程是一种轻量级的用户级线程,能够在运行时挂起并恢复执行,保持自己的局部状态。与传统的回调和事件循环相比,协程能让异步代码以同步写法显现,极大提升可读性与可维护性。
1.1 关键语言特性
co_await、co_yield、co_return:三大协程关键词,分别用于挂起、产生值、返回结果。std::future/std::promise替代:协程不再需要显式的 Promise/Future 组合,返回值为std::future或自定义 awaitable 对象即可。- 协程句柄
std::coroutine_handle:管理协程生命周期,可主动销毁或恢复。
2. 实现原理
协程编译时会将函数体拆分成若干基本块,在每个挂起点生成一个 resume 函数,恢复时会跳转回该点。
- 悬挂点:
co_await、co_yield、co_return会在编译时生成对应的 suspend/resume 逻辑。 - 状态机:协程内部状态由一个 promise_type 对象维护,包含协程局部变量和挂起/恢复状态。
- 堆分配:协程对象的状态通常放在堆上,避免栈空间碎片,且可随时扩展。
3. C++23 的改进
std::generator:提供统一的生成器类型,简化co_yield的使用。std::ranges::subrange:与协程返回的范围配合,支持按需遍历。- 异步 I/O 集成:
std::experimental::net的awaitable接口与协程配合使用,支持 POSIX 事件循环。 - 同步文件 I/O:
std::filesystem::file提供awaitable接口,直接在协程里读取文件。
4. 典型使用案例
4.1 网络服务器
#include <iostream>
#include <netinet/in.h>
#include <unistd.h>
#include <experimental/coroutine>
using namespace std::experimental::literals;
struct AwaitableAccept {
int sock;
int operator()() const { return accept(sock, nullptr, nullptr); }
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
// 简化示例:直接挂起
h.resume();
}
int await_resume() const noexcept { return operator()(); }
};
struct EchoServer {
int listen_fd;
auto operator()() -> std::experimental::coroutine_handle<> {
while (true) {
int client = co_await AwaitableAccept{listen_fd};
// 简化处理:读取并写回
char buf[512];
int n = read(client, buf, sizeof buf);
write(client, buf, n);
close(client);
}
}
};
int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{AF_INET, htons(8080), INADDR_ANY};
bind(fd, (sockaddr*)&addr, sizeof addr);
listen(fd, 128);
EchoServer{fd}(); // 启动协程
// 事件循环(简化)
std::this_thread::sleep_for(10s);
close(fd);
}
4.2 并行任务调度
#include <vector>
#include <thread>
#include <future>
#include <experimental/coroutine>
struct Task {
int id;
std::string name;
};
struct AwaitableTask {
Task t;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
std::thread([=]{
std::cout << "Running " << t.name << " (id=" << t.id << ")\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
auto worker(Task t) -> std::experimental::coroutine_handle<> {
co_await AwaitableTask{t};
}
int main() {
std::vector <Task> tasks = {{1, "A"}, {2, "B"}, {3, "C"}};
for (auto &t : tasks) worker(t)(); // 启动所有协程
std::this_thread::sleep_for(std::chrono::seconds(3));
}
5. 性能与调试
- 栈占用:协程本身不占用栈,状态存放在堆上,避免栈溢出。
- 调试难度:断点设置时需关注协程挂起点,IDE 需支持协程调试。
- 异常传播:
promise_type::final_suspend负责把异常抛给await_resume。
6. 结语
C++23 的协程与同步 I/O 生态,为开发者提供了高效、易读的异步编程模型。掌握 co_await 与标准库协程工具后,您可以将传统的回调地狱转化为清晰的协程链,极大提升大型项目的可维护性与性能。
祝你在 C++ 的协程世界里玩得开心!