《C++23 Coroutines:从生成器到异步IO的协程实践》
·
一、协程的核心概念:从“函数”到“可挂起的计算”
C++23的协程(Coroutines)是一种“可挂起、可恢复”的函数——它能在执行过程中暂停(保存状态),后续再从暂停处继续执行。这与传统函数的“一次性执行到底”形成根本区别,完美适配惰性序列(如斐波那契数列)、异步IO(如非阻塞网络请求)等场景。
1. 协程的关键组件
理解协程,需掌握三个核心概念:
- 协程函数:返回协程对象的函数(用
co_await/co_yield/co_return修饰); - Promise类型:控制协程的生命周期(初始化、挂起、恢复、返回值);
- 协程句柄(Coroutine Handle):用于手动管理协程帧(如恢复、销毁)。
二、实战1:用std::generator实现惰性序列(斐波那契数列)
std::generator是C++23提供的基于协程的生成器,专门用于实现“惰性求值”——仅在需要时生成下一个元素,避免一次性计算所有值(节省内存,支持无限序列)。
1. 斐波那契数列生成器
#include <coroutine>
#include <generator>
#include <iostream>
// 返回一个生成<int>的协程
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a; // 挂起协程,返回当前值a
auto next = a + b;
a = b;
b = next;
}
}
void test_fibonacci() {
auto fib_gen = fibonacci(); // 创建生成器(协程未执行)
// 逐步获取斐波那契数(每次推进协程)
std::cout << "Fibonacci(前10项): ";
for (int i = 0; i < 10; ++i) {
std::cout << *fib_gen++ << " "; // *解引用取值,++推进协程
}
// 输出:0 1 1 2 3 5 8 13 21 34
}
2. std::generator的底层逻辑
- 协程挂起:
co_yield a会将a存入生成器的“缓存”,并将协程暂停在当前位置; - 协程恢复:调用
++fib_gen时,协程从co_yield后继续执行,计算下一个值; - 内存高效:仅需保存当前状态(
a、b),无需预计算所有元素。
三、实战2:自定义Promise类型——控制协程行为
std::generator封装了Promise类型的细节,但自定义协程需显式定义Promise类型,以控制初始化、挂起、返回值等行为。
1. 自定义协程:返回固定值
#include <coroutine>
#include <iostream>
// 1. 定义Promise类型(控制协程行为)
struct SimplePromise {
int result; // 存储协程的返回值
// 初始挂起策略:协程启动后立即执行(不挂起)
std::suspend_never initial_suspend() noexcept { return {}; }
// 最终挂起策略:协程结束后不挂起(直接销毁)
std::suspend_never final_suspend() noexcept { return {}; }
// 异常处理:终止程序(简化示例)
void unhandled_exception() noexcept { std::terminate(); }
// 设置返回值:当协程执行co_return时调用
void return_value(int val) noexcept { result = val; }
// 创建Promise对象本身(默认构造)
SimplePromise() = default;
};
// 2. 定义协程的返回对象(关联Promise类型)
struct SimpleCoroutine {
using promise_type = SimplePromise;
int value;
// 从Promise句柄构造返回对象
explicit SimpleCoroutine(std::coroutine_handle<SimplePromise> handle)
: value(handle.promise().result) {}
};
// 3. 协程函数:返回SimpleCoroutine
SimpleCoroutine simple_coroutine() {
co_return 42; // 协程返回42(会调用Promise的return_value)
}
void test_simple_coroutine() {
auto coro = simple_coroutine();
std::cout << "Simple Coroutine Result: " << coro.value << std::endl;
// 输出:42
}
2. 关键点解析
- Promise类型:定义了协程的“行为契约”——比如
initial_suspend决定是否立即执行; - 协程返回对象:通过Promise句柄获取协程的结果(
handle.promise().result); - co_return:协程的“返回语句”,会将值传递给Promise的
return_value。
四、实战3:用协程简化异步IO(模拟网络请求)
传统异步IO依赖“回调地狱”或“Future/Promise链”,而协程用同步写法实现异步,代码更简洁、易读。
1. 模拟异步网络请求
#include <coroutine>
#include <iostream>
#include <optional>
#include <thread>
#include <chrono>
#include <string>
// 1. 模拟异步操作的结果
struct AsyncData {
std::string content;
};
// 2. 定义异步操作的Promise类型
struct AsyncPromise {
AsyncData data; // 存储异步结果
std::suspend_always initial_suspend() noexcept { return {}; } // 启动后挂起(等待异步操作)
std::suspend_always final_suspend() noexcept { return {}; } // 结束后挂起(方便获取结果)
void unhandled_exception() noexcept { std::terminate(); }
void return_value(AsyncData d) noexcept { data = d; }
};
// 3. 定义异步操作的返回对象(句柄)
struct AsyncOperation {
using promise_type = AsyncPromise;
std::coroutine_handle<AsyncPromise> handle;
explicit AsyncOperation(std::coroutine_handle<AsyncPromise> h) : handle(h) {}
~AsyncOperation() { if (handle) handle.destroy(); } // 手动销毁协程帧
AsyncData get() { return handle.promise().data; } // 获取结果
};
// 4. 协程函数:模拟异步网络请求
AsyncOperation async_fetch(const std::string& url) {
// 模拟网络延迟(实际中可替换为真实的异步IO操作,如epoll、io_uring)
std::this_thread::sleep_for(std::chrono::seconds(1));
// co_return异步结果(协程挂起,直到调用get()获取结果)
co_return AsyncData{"Content from " + url};
}
// 5. 主函数:用协程处理异步请求
int main() {
std::cout << "Start fetching data..." << std::endl;
// 启动异步任务(协程挂起,主线程继续执行)
AsyncOperation task = async_fetch("https://example.com");
// 模拟主线程做其他事(如处理UI、计算)
std::cout << "Do other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
// 获取异步结果(协程恢复,返回数据)
AsyncData result = task.get();
std::cout << "Received: " << result.content << std::endl;
return 0;
}
2. 异步IO的优势
- 同步写法:用
co_return代替回调,代码逻辑线性化; - 状态管理:协程帧自动保存异步操作的状态(如url、delay时间);
- 可组合性:可将多个异步操作用
co_await顺序组合(无需嵌套回调)。
五、C++23协程的“隐藏价值”
- 性能提升:协程帧复用栈空间,避免传统递归或回调的内存开销;
- 代码可读性:用同步写法实现异步,降低认知负担;
- 生态扩展:为C++异步编程库(如Asio、Libuv)提供了更简洁的抽象层。
六、总结:协程是“下一代异步编程”的基石
C++23协程不是“语法糖”,而是重新定义了“函数”的边界——它让“暂停-恢复”成为一等公民,完美适配惰性序列、异步IO等场景。无论是用std::generator生成无限序列,还是用协程简化网络请求,都能感受到“代码更简洁、逻辑更清晰”的魅力。
一句话记住C++23协程:
它让“等待”不再是阻塞,让“生成”不再是预计算——用同步的方式,写异步的代码。
参考资料:
- C++23标准草案—— Chapter 18 “Coroutines”;
- 《C++23 Coroutines: A Practical Guide》(cppreference);
- Bjarne Stroustrup的博客——“Coroutines: Making Asynchronous Programming Bearable”;
- Louis Dionne的演讲——“C++20/23 Coroutines: From Theory to Practice”。
更多推荐
所有评论(0)