C++26 执行控制库:走向统一的异步编程模型
1. C++26 执行控制库:走向统一的异步编程模型
异步编程在现代 C++ 中扮演着越来越重要的角色。从传统的回调、std::future到协程,我们一直在寻找更优雅、更高效的方式来处理并发和 I/O 密集型任务。C++26 即将引入的执行控制库提供了一个统一的、可组合的异步任务模型——发送器与接收器,旨在解决现有异步 API 碎片化、难以组合和维护的问题。本文将带你了解这一革命性变化的背景、核心设计,并通过实例展示其用法。
2. 为什么需要统一的异步模型?
在 C++ 的发展历程中,异步编程经历了多个阶段:
- C++11 的
std::future/std::promise:提供了基本的 future/promise 模型,但组合能力弱,容易导致阻塞。 - C++20 的协程:极大改善了异步代码的可读性,允许以同步方式编写异步逻辑。然而,协程的底层仍然依赖具体执行器,不同库之间的异步类型难以互操作。
- 第三方库各自为政:ASIO、Boost.Fiber、libunifex 等都有自己的异步抽象,想要桥接它们需要大量胶水代码。
C++26 执行控制库的目标就是提供一个零成本的通用抽象,让所有异步计算都统一为“发送器-接收器”这一对概念,从而实现真正的可组合性。
3. 核心概念:发送器和接收器
执行控制库的核心由三个词构成:
- 发送器:描述一个可以异步启动的工作单元。它本身不执行任何操作,只是描述了“做什么”以及“在哪个执行器上做”。
- 接收器:作为回调的集合,定义了如何消费异步操作的结果(成功值、错误、完成信号)。每一个发送器都需要连接到一个接收器才能启动。
- 操作状态:当发送器与接收器连接后产生的对象,表示一个正在进行中的异步操作,管理其生命周期。
简单来说,一个异步计算的抽象就是:auto op = connect(sender, receiver);,然后调用start(op)来启动。这种设计将“描述计算”与“执行计算”完全解耦,为复杂的组合和调度优化打开了大门。
4. 可组合的异步管道
执行控制库最大的优势在于其强大的组合能力。类似于 ranges,你可以通过一系列算法将发送器串联成异步管道。以下是一些核心的组合操作:
then():在异步结果返回后对其进行转换。upon_error():处理可能发生的错误,并返回另一个发送器。let_value()、let_error():根据前序结果动态选择下一个发送器。split():将只能使用一次的发送器变为可共享的多投版。when_all():等待多个发送器并发完成。
此外,start_detached()提供了一种“发射后不管”的便利启动方式。
5. 调度器与执行上下文
异步操作离不开执行器的概念。执行控制库将执行上下文封装为调度器,一个调度器可以从执行上下文中得到一个发送器,当这个发送器启动时,会在该调度器关联的执行环境上运行任务。例如:
run_loop:一个简单的单线程事件循环,适合测试和小规模任务。thread_pool:线程池调度器,可以在多个工作线程上并发执行任务。- 可定制执行器:允许用户接入自己的 GPU 调度器、DPDK 调度器等。
通过这种抽象,无论底层是线程池还是协程,上层的异步管道代码都可以保持不变,大大提高了代码的可移植性。
6. 实战示例
下面是一个简单的示例,展示如何使用 C++26 执行控制库实现异步操作:
#include <execution> #include <iostream> using namespace std::execution; int main() { // 获取一个线程池调度器 thread_pool pool(4); auto sched = pool.get_scheduler(); // 构造一个异步管道: // 1. 在调度器上启动 // 2. 返回一个整型值 42 // 3. 对其加 1 auto work = schedule(sched) | then([] { return 42; }) | then([](int x) { return x + 1; }); // 启动并获取结果(通过同步等待,仅供示意) auto [result] = sync_wait(work); std::cout << "Result: " << result << std::endl; return 0; }在这个例子中,schedule(sched)返回一个发送器,随后通过then逐步构建计算逻辑。最后使用sync_wait从管道中同步提取结果。注意,then并不直接执行代码,只是描述转换,真正执行发生在连接接收器并启动操作状态之后。
7. 与协程的协同工作
执行控制库与 C++20 协程可以无缝协同。你可以使用co_await来等待一个发送器完成,也可以将协程包装为发送器。这为混合使用提供了极大的灵活性。例如:
task<int> async_computation() { auto sched = co_await this_thread::scheduler; int val = co_await schedule(sched) | then([]{ return 42; }); co_return val + 1; }这使得异步代码在协程管理下仍然可以享受执行控制库的组合与调度优势。
8. 总结与展望
C++26 的执行控制库是 C++ 异步编程走向成熟的重要里程碑。它提供了统一、可组合、零开销的异步抽象,解决了长期以来的库间互操作难题。通过发送器/接收器模型,开发者可以编写一次异步逻辑,并在不同的执行环境(线程池、事件循环、协程等)中重复使用。虽然标准库实现仍在完善中,但该设计的核心思想已经在 P2300 提案中经受了长时间的打磨,值得每一位 C++ 开发者提前了解和实践。