在 C++20 标准中,协程(Coroutine)作为一种极具前瞻性的语法特性,为异步编程带来了全新的视角。协程既可以像普通函数那样使用,也可以像生成器一样在多次调用间保持状态。本文将从协程的基本概念入手,逐步介绍协程的实现机制、关键关键字以及在实际项目中的应用场景,并通过示例代码演示如何在 C++20 环境下使用协程完成异步任务与生成器功能。
1. 协程的基本概念
协程是一种能够挂起并恢复执行的函数。不同于线程,协程共享同一线程的栈空间,切换协程的成本极低。协程的核心在于“挂起点”(co_yield, co_return, co_await)和“恢复点”,当协程挂起时,当前上下文被保存,随后可以在任何位置恢复执行。
协程可以分为三类:
- 生成器:每次
co_yield输出一个值,调用者通过迭代器逐步获取。 - 任务(Task):表示一个异步操作,返回一个
future或task对象。 - 等待器(Awaitable):定义
await_ready,await_suspend,await_resume三个成员,决定协程如何等待。
2. 关键字与类型
2.1 co_yield
用于生成器,返回一个值并挂起协程,让调用者获取该值。每次 co_yield 之后,协程状态保持在当前位置,下一次迭代将从此位置继续。
2.2 co_return
终止协程,并可返回一个最终值。对于 Task 类型,co_return 通常是协程完成时的返回值。
2.3 co_await
让协程等待一个 awaitable 对象。协程在此处挂起,等待条件满足后恢复执行。
2.4 co_resume / co_await 的返回类型
在 C++20 中,协程需要使用 std::experimental::generator、std::experimental::task 或者自定义协程类型。标准库提供了 std::generator 与 std::task,但需要 -std=c++20 并开启实验特性。
3. 协程的实现机制
协程的编译实现通常包括:
- 状态机生成:编译器将协程函数转换为一个状态机类,维护协程的状态(
enum或int)。 - 悬挂与恢复:使用
std::coroutine_handle保存上下文,resume()调用恢复执行。 - 栈保存:编译器会把本地变量和返回地址保存在堆或自定义栈中,保证协程挂起时状态完整。
3.1 自定义协程类型
示例代码展示如何自定义一个简单的协程类型 `generator
`: “`cpp template 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 ::from_promise(*this)}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle coro; explicit generator(std::coroutine_handle h) : coro(h) {} ~generator() { if (coro) coro.destroy(); } struct iterator { std::coroutine_handle coro; iterator(std::coroutine_handle h) : coro(h) {} iterator& operator++() { coro.resume(); if (!coro.done()) return *this; return *this; } T operator*() const { return coro.promise().current_value; } bool operator==(std::default_sentinel_t) const { return coro.done(); } }; iterator begin() { coro.resume(); return iterator{coro}; } std::default_sentinel_t end() { return {}; } }; “` 使用方式: “`cpp generator count_to_n(int n) { for (int i = 1; i <= n; ++i) co_yield i; } “` ## 4. 实际应用示例 ### 4.1 异步 I/O 读取文件 假设我们有一个异步文件读取库 `AsyncFileReader`,提供 `awaitable readLine()`。我们可以写一个协程来读取多行: “`cpp #include #include #include generator read_file_lines(const std::string& filename) { AsyncFileReader reader(filename); while (true) { std::string line = co_await reader.readLine(); if (line.empty()) break; // EOF co_yield line; } } “` 调用: “`cpp for (auto& line : read_file_lines(“data.txt”)) std::cout << line << '\n'; “` ### 4.2 并发计算 使用 `std::future` 与协程结合,可以实现非阻塞的并发计算: “`cpp #include #include task async_sum(std::vector nums) { int sum = 0; for (int x : nums) sum += x; co_return sum; } int main() { auto task1 = async_sum({1,2,3,4,5}); auto task2 = async_sum({6,7,8,9,10}); std::cout << "Sum1: " << task1.get() << '\n'; std::cout << "Sum2: " << task2.get() << '\n'; } “` ## 5. 性能与注意事项 – **栈使用**:协程的栈分配与传统栈不同,编译器通常在堆上分配。过度使用会导致内存碎片,需要注意。 – **异常传播**:协程在异常抛出时会通过 `unhandled_exception` 处理,务必提供异常安全的实现。 – **与线程交互**:协程在同一线程内切换,若与多线程交互需使用线程安全的同步机制。 ## 6. 结语 C++20 协程为异步编程与生成器提供了强大且易用的语法糖。掌握协程的核心概念与实现细节,能够帮助开发者写出更高效、可维护的代码。未来,随着 C++23 对协程的进一步完善,协程将在更多场景中得到广泛应用。祝你在 C++20 的协程旅程中不断探索、不断进步。