协程(coroutine)是 C++20 标准中新加入的强大语法特性,它为实现轻量级协作式并发、异步 I/O、生成器等场景提供了统一而简洁的语法。本文将从协程的基本概念、关键字、返回类型、实现方式以及一个简易的异步任务示例,逐步展开讲解,帮助你快速掌握协程的使用方法。
1. 协程基础
1.1 什么是协程
协程是一种函数级别的协作式多任务单元,它允许函数在执行过程中“挂起”并在稍后恢复执行,而不需要线程切换。与线程相比,协程的切换成本更低,能够更好地控制执行顺序。
1.2 关键字
co_await: 等待一个 awaitable 对象完成。类似于await。co_yield: 产生一个值给调用者。用于实现生成器。co_return: 返回协程的最终值。
1.3 协程的生命周期
- 生成:调用协程函数后得到一个
promise_type对象。 - 执行:协程开始执行直到遇到
co_await/co_yield/co_return。 - 挂起:遇到
co_await/co_yield时协程挂起,状态保存在promise_type中。 - 恢复:外部事件(如异步 I/O 完成)触发协程恢复执行。
2. 协程返回类型
C++20 规定协程函数的返回类型必须满足 std::experimental::coroutine_handle 的 promise_type 约束。最常见的返回类型有:
- `std::future `(标准库)
- `std::experimental::generator `(实验性)
- 自定义 `Task `,内部使用 `std::coroutine_handle` 管理协程状态。
2.1 自定义 Task 示例
template<typename T>
struct Task {
struct promise_type {
T value;
std::exception_ptr exception;
Task get_return_object() {
return Task{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { exception = std::current_exception(); }
template<typename U>
void return_value(U&& v) { value = std::forward <U>(v); }
};
std::coroutine_handle <promise_type> coro;
explicit Task(std::coroutine_handle <promise_type> h) : coro(h) {}
~Task() { if (coro) coro.destroy(); }
T get() {
if (coro.promise().exception) std::rethrow_exception(coro.promise().exception);
return coro.promise().value;
}
};
3. 协程的 awaitable 对象
任何类型只要满足 operator co_await,或者提供 await_ready/await_suspend/await_resume 成员函数,即可作为 awaitable。常见的 awaitable:
- `std::future `(可等待异步结果)
- `std::experimental::generator `(可等待下一个生成值)
- 自定义异步 I/O 对象,例如 `asio::awaitable `(Boost.Asio)
3.1 await_ready/await_suspend/await_resume
struct SimpleAwaitable {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 例如注册回调,完成后调用 h.resume()
}
int await_resume() const noexcept { return 42; }
};
4. 典型使用场景
4.1 生成器
#include <experimental/generator>
std::experimental::generator <int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
4.2 异步 I/O
#include <asio.hpp>
#include <iostream>
asio::awaitable <void> async_read_file(const std::string& path) {
asio::file_handle file{ co_await asio::this_coro::executor };
// ...
std::string data = co_await file.async_read_some(...);
std::cout << "Read " << data.size() << " bytes\n";
}
4.3 简易网络服务器
asio::awaitable <void> session(tcp::socket sock) {
try {
for (;;) {
std::array<char, 1024> buf;
std::size_t n = co_await sock.async_read_some(asio::buffer(buf));
if (n == 0) break;
co_await sock.async_write_some(asio::buffer(buf, n));
}
} catch (...) { /* 处理异常 */ }
}
asio::awaitable <void> server(tcp::acceptor& acceptor) {
for (;;) {
tcp::socket sock{ co_await acceptor.async_accept() };
asio::co_spawn(acceptor.get_executor(), session(std::move(sock)), asio::detached);
}
}
5. 性能与注意事项
- 协程不是线程:挂起/恢复是轻量级的,但仍需注意同步与数据竞争。
- 内存分配:协程的
promise_type通常在堆上分配,避免频繁new/delete可以使用协程池或自定义分配器。 - 异常传播:异常会保存在
promise_type中,通过co_return或get()传播。 - 标准库支持:C++20 标准库中已提供
std::future/std::generator,但许多实际项目仍依赖第三方库(如 Boost.Asio、cppcoro)。
6. 结语
协程为 C++20 带来了更高层次的异步编程模型,既能保持代码可读性,又能减少回调地狱。掌握协程的核心概念、关键字以及常见的 awaitable 类型后,你可以在网络编程、游戏逻辑、数据流处理等领域大显身手。建议先从简单的生成器练手,再逐步尝试异步 I/O,最后在项目中逐步替换传统回调或线程模型,享受协程带来的优雅与高效。