C++17 中并未正式引入协程(coroutines),但它为协程的实现奠定了重要基础。协程是一种轻量级的子程序,能够在执行过程中挂起并在需要时恢复,适合处理异步IO、流式数据处理和状态机等场景。本文从概念入手,阐述协程在 C++ 生态中的作用,并给出一个基于 C++20 标准的协程实现示例,帮助读者快速上手。
1. 协程的核心概念
-
挂起与恢复
协程在运行过程中可以被挂起(co_await、co_yield、co_return),挂起点会保存协程的执行状态(如局部变量、指令指针)。随后,当协程再次被调用时,执行从挂起点继续。 -
协程句柄(
std::coroutine_handle)
协程句柄是对协程的引用,负责控制协程的生命周期、挂起与恢复。 -
协程类型
generator:可产生一系列值的协程,类似于 Python 的生成器。task:异步任务,最终返回一个结果或抛异常。
-
awaitable
一个可等待的对象,它实现了await_ready(),await_suspend(),await_resume()三个成员函数。
2. C++17 的前瞻性支持
虽然 C++17 没有正式的协程语法,以下特性为后续实现做准备:
std::experimental::coroutine_traits:定义协程返回类型。std::experimental::coroutine_handle:协程句柄的实验性实现。co_await、co_yield、co_return的语法已在实验阶段。
这些实验性特性在 C++20 标准中被正式引入,语法和库实现更完善。
3. C++20 协程的完整实现示例
下面给出一个简单的异步任务实现,用于模拟网络请求的非阻塞读取。
#include <iostream>
#include <coroutine>
#include <string>
#include <chrono>
#include <thread>
// 1. awaitable:模拟异步延迟
struct Delay {
std::chrono::milliseconds ms;
bool await_ready() const noexcept { return ms.count() == 0; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
std::thread([h, ms=ms]{
std::this_thread::sleep_for(ms);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
// 2. task:异步任务类型
template<typename T>
struct Task {
struct promise_type {
T value_;
Task get_return_object() {
return Task{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_value(T value) { value_ = std::move(value); }
};
std::coroutine_handle <promise_type> handle_;
Task(std::coroutine_handle <promise_type> h) : handle_(h) {}
~Task() { if (handle_) handle_.destroy(); }
T get() { return handle_.promise().value_; }
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept { handle_.resume(); }
T await_resume() const noexcept { return handle_.promise().value_; }
};
// 3. 异步函数
Task<std::string> fetch_from_network() {
std::cout << "开始请求...\n";
co_await Delay{std::chrono::milliseconds(2000)}; // 模拟延迟
std::cout << "网络返回完成。\n";
co_return "Hello, Coroutine!";
}
int main() {
auto task = fetch_from_network();
std::cout << "等待结果...\n";
std::cout << "结果: " << task.get() << '\n';
return 0;
}
说明
Delay是一个 awaitable,用来模拟异步延迟。Task是一个简单的异步任务包装,支持co_return返回值。fetch_from_network使用co_await挂起并在延迟完成后恢复执行。
4. 协程的应用场景
| 场景 | 典型用途 |
|---|---|
| 异步IO | 网络通信、文件读取、数据库查询 |
| 流式数据 | 数据流处理、事件驱动系统 |
| 状态机 | 游戏 AI、协议解析、渲染管线 |
| 并发控制 | 协程调度器、轻量级线程替代 |
5. 性能与注意事项
- 堆栈消耗:协程在挂起时会保存局部变量,若过度使用可能导致堆栈膨胀。
- 错误处理:协程异常必须通过
promise_type::unhandled_exception()或co_await的异常处理来捕获。 - 调度策略:默认协程是协作式,需要自行调度(如
co_await的await_suspend实现)。若需抢占式调度,可结合线程池或事件循环。
6. 结语
C++ 通过标准化协程,提供了更简洁、更高效的异步编程模型。虽然 C++17 已经为协程打下了实验性基础,但真正的生态落地还是在 C++20 之后。掌握协程的基本概念与实现方式后,你可以在项目中轻松构建高性能、可维护的异步代码。祝你编程愉快!