协程(coroutine)是 C++20 引入的一项强大功能,旨在简化异步编程和生成器的实现。与传统的回调或线程相比,协程通过暂停和恢复执行,允许程序在保持单线程执行的同时实现类似多线程的并发效果。本文将从协程的基本概念、关键语法到实际应用,带你快速入门。
一、协程基本概念
协程可以被视为“轻量级线程”,它们能够在执行过程中暂停(co_await、co_yield 或 co_return),随后恢复。核心特性包括:
- 挂起点(suspend point):程序在此停留,等待外部条件或事件。
- 恢复点(resume point):挂起后再次激活,继续执行。
- 协程句柄(
std::coroutine_handle):控制协程生命周期的对象。
协程本质上是生成器与异步操作的统一表达方式。
二、关键语法
- 协程函数声明
std::generator <int> my_generator() {
for (int i = 0; i < 5; ++i) {
co_yield i; // 产生值
}
}
- `std::generator ` 是 C++20 中提供的协程返回类型,表示一个可迭代的生成器。
co_yield暂停执行,返回当前值;后续迭代会恢复。
- 异步协程
std::future <int> async_add(int a, int b) {
co_return a + b; // 立即返回值,异步上下文会自动包装为 std::future
}
co_return结束协程,生成一个 `std::future `。
- 挂起与恢复
std::generator <int> range(int start, int end) {
for (int i = start; i <= end; ++i) {
co_yield i;
}
}
调用方:
for (int v : range(1, 10)) {
std::cout << v << ' ';
}
三、实现自定义生成器
虽然 std::generator 已提供,但在某些场景需要自定义协程返回类型。以下示例展示如何实现一个简单的整数生成器。
#include <coroutine>
#include <iostream>
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 {}; }
generator get_return_object() {
return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
explicit generator(handle_type h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
generator(const generator&) = delete;
generator& operator=(const generator&) = delete;
generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; }
generator& operator=(generator&& other) noexcept {
if (this != &other) {
if (coro) coro.destroy();
coro = other.coro;
other.coro = nullptr;
}
return *this;
}
T next() {
coro.resume();
return coro.promise().current_value;
}
bool done() const { return coro.done(); }
};
使用示例:
generator <int> seq() {
for (int i = 0; i < 5; ++i) {
co_yield i * 2;
}
}
int main() {
auto g = seq();
while (!g.done()) {
std::cout << g.next() << ' ';
}
}
四、协程在异步 I/O 的应用
协程最常见的场景之一是网络 I/O。假设我们使用一个异步 I/O 库(如 Boost.Asio 或 std::net),协程可大幅简化代码:
#include <asio.hpp>
#include <iostream>
#include <coroutine>
asio::awaitable <void> handle_client(tcp::socket socket) {
try {
char data[1024];
std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
std::cout << "Received: " << std::string(data, n) << std::endl;
co_return;
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
}
}
这里 co_await 暂停协程,直到 I/O 完成。整个流程保持同步可读性,同时不阻塞线程。
五、协程的性能与注意事项
- 栈分配:协程的栈由实现决定,通常是动态分配的,避免了大栈的使用。
- 生命周期:协程句柄必须在协程结束前保持有效;使用
std::generator时,迭代器内部已管理。 - 异常传播:异常会被
promise_type::unhandled_exception()捕获,可自定义异常处理。 - 与线程混用:协程本身不创建线程,需与线程池或事件循环结合使用。
六、总结
C++20 协程为开发者提供了统一、高效的异步编程模型。无论是生成器、异步 I/O,还是复杂的协程链式调用,都能在保持代码可读性的同时提升性能。通过本文的示例,你已掌握协程的基本语法、实现方式以及常见应用场景。下一步可以尝试将协程与多线程池、任务调度器结合,进一步探索更高级的并发架构。祝编码愉快!