C++20 标准引入了协程(coroutines)机制,为编写异步、事件驱动和流式编程提供了语言级别的支持。本文将从协程的基本概念、核心语法、协程框架的实现原理以及实际应用场景入手,帮助读者快速上手并在项目中灵活运用协程。
1. 协程的核心概念
1.1 什么是协程?
协程是一种轻量级的线程,能够在执行过程中“挂起”和“恢复”,从而实现非阻塞式的等待和切换。与传统线程相比,协程的切换成本极低(只涉及栈帧状态的保存/恢复),而且编程模型更接近同步代码。
1.2 协程的关键字
C++20 引入了三大关键字:
co_await:等待一个异步操作完成,类似await。co_yield:从协程中产生一个值,类似yield。co_return:终止协程并返回最终结果。
1.3 协程的类型
协程在实现时需要一个 协程句柄(coroutine handle),并且根据 co_await 的返回类型决定协程的返回类型。常见的返回类型包括:
- `std::future `:返回一个标准的 `future`。
- `generator `:生成器,用于流式输出。
- `task `:自定义异步任务类型。
2. 基本语法与使用示例
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
// 1. 一个简单的协程返回整型
std::future <int> async_add(int a, int b) {
std::cout << "start async_add\n";
co_await std::suspend_always{}; // 模拟异步延迟
co_return a + b;
}
int main() {
auto fut = async_add(3, 5);
std::cout << "waiting for result...\n";
std::cout << "result = " << fut.get() << '\n';
return 0;
}
上述代码演示了一个最基本的协程:async_add 在内部挂起一次,然后返回结果。std::suspend_always 是一个协程悬挂点,实际项目中可以替换为网络 I/O、定时器等异步事件。
3. 协程生成器(Generator)
协程生成器可用于在迭代器语义下产生一系列值,类似于 Python 的 yield。
template<typename T>
struct generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) { current_value = value; return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
generator get_return_object() {
return generator{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
explicit generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~generator() { if (handle) handle.destroy(); }
bool move_next() { handle.resume(); return !handle.done(); }
T current_value() const { return handle.promise().current_value; }
};
generator <int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int temp = a + b;
a = b; b = temp;
}
}
int main() {
for (auto it = fibonacci(10); it.move_next(); ) {
std::cout << it.current_value() << ' ';
}
std::cout << '\n';
}
4. 协程与事件循环
协程最典型的应用场景是实现事件循环(Event Loop)。以下示例使用 asio 作为 I/O 库,演示如何在事件循环中使用协程进行网络编程。
#include <asio.hpp>
#include <iostream>
#include <coroutine>
asio::awaitable <void> echo_server(asio::ip::tcp::acceptor& acceptor) {
for (;;) {
auto socket = co_await acceptor.async_accept(asio::use_awaitable);
std::cout << "client connected\n";
co_spawn(socket.get_executor(),
[socket = std::move(socket)]() mutable -> asio::awaitable <void> {
char data[1024];
std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
co_await asio::async_write(socket, asio::buffer(data, n), asio::use_awaitable);
}, asio::detached);
}
}
int main() {
asio::io_context io;
asio::ip::tcp::acceptor acceptor(io, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 12345));
co_spawn(io, echo_server(acceptor), asio::detached);
io.run();
}
5. 结合线程池实现高性能异步
C++20 协程与线程池结合,可实现高吞吐量的异步任务执行。常见做法是使用 std::async 或自定义 task 对象,将协程的结果推入线程池的任务队列。
template<typename T>
struct task {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
handle_type handle;
struct promise_type {
T value;
std::exception_ptr exc;
auto get_return_object() { return task{ handle_type::from_promise(*this) }; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { exc = std::current_exception(); }
void return_value(T v) { value = v; }
task get_return_object() { return task{ handle_type::from_promise(*this) }; }
};
task(handle_type h) : handle(h) {}
~task() { if (handle) handle.destroy(); }
T get() {
handle.resume();
if (handle.promise().exc) std::rethrow_exception(handle.promise().exc);
return handle.promise().value;
}
};
task <int> async_square(int x) {
co_return x * x;
}
int main() {
auto t = async_square(7);
std::cout << "square = " << t.get() << '\n';
}
6. 常见坑与调试技巧
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 协程未挂起 | co_await 前后无任何异步对象 |
确认 co_await 的对象实现了 await_ready/await_suspend/await_resume |
| 协程生命周期 | 协程句柄已销毁后再使用 | 通过 std::shared_ptr 或 std::future 管理协程对象 |
| 多线程协程 | 直接在多个线程中使用同一个协程句柄 | 协程非线程安全,需使用同步机制或在各线程创建独立协程 |
| 资源泄漏 | 协程未正确 destroy |
在 promise_type 的 final_suspend 里返回 std::suspend_always{} 并手动销毁句柄 |
调试协程时,可使用 -fcoroutines 编译器选项,并借助 gdb 的 info coroutine 命令查看协程状态。
7. 结语
C++20 协程为开发者提供了一种更自然、更高效的异步编程模型。掌握 co_await/ co_yield/ co_return 的使用,以及协程句柄、Promise、awaitable 对象的设计思路,能够让你在网络编程、游戏引擎、并发系统等领域快速构建出高性能、可维护的代码。希望本文的示例与经验能为你开启协程之旅提供助力。