NeFut Logo NeFut
EN 管理员登录

[C++黑魔法] C++20 协程在异步网络编程中的潜力探讨

发布于:2026-06-11 09:00 最后更新:2026-06-12 02:58
#algorithm #C++ #Async

大家好!我最近几个月一直在研究 C++ 协程。起初,我开始构建一个基于 io_uring 的异步运行时,以更好地理解 C++ 无栈协程模型,结果发现编写异步代码的感觉与其他语言一样干净可读。随后,我希望进一步探讨协程模型在简化异步编程中的应用,因此决定在我的运行时基础上构建一个异步 Redis 客户端库。在构建 Redis 客户端库的过程中,我很快意识到我的运行时缺少一些复杂工作流的工具,例如 when_allwhen_any 组合器。但在 C++ 协程模型强大的表达能力的帮助下,这个问题很快得到了解决,最终我实现了一个我期望的简单接口,如下所示:

auto [_1, _2, _3, exec_res] = co_await redis.multi()
    .set("user:{1001}", "val1")
    .set("item:{1001}", "val2")
    .exec();

在整个过程中,我通过 promise_type 实验了不同的任务类型,深入理解了 Awaiterstd::coroutine_handle<> 背后的机制。因此,我现在相信当前的 C++ 协程模型大大降低了异步编程的复杂性,除了……

取消机制

取消单个 IO(如 recv/send)似乎简单,因为运行时已经提供了该功能。然而,当你尝试将这种能力扩展到任务级别时,事情就变得复杂。例如:考虑任务 A 正在等待 B 或 C,而 C 将等待 D(A->B/C->D)。在这种情况下,手动注册异步调用树中的每个 IO 以便取消将是一个噩梦,我们可能只想调用 A.cancel() 或从 A 派生一个取消令牌,而不是检查 D 中的单个 IO。此外,C 的取消可能不会影响 B,但会取消 D。

std::execution 描述了 stoppable_tokenset_stopped() 来实现这一目标,但这需要每个接收者非常小心地实现,以检查停止令牌。基于协程的 IO 表示令牌可能隐藏在 promise_type 中,只要根挂起节点记得检查是否已停止,并在调用链中注册其回调,使用 await_suspendawait_transform() 中的一些技巧,如:

template<class Promise>
bool await_suspend(std::coroutine_handle<Promise> h) {
    if constexpr (requires { h.promise().hook(this); }) {
        bool stopped = h.promise().hook(this);
        if (stopped) {
            return false;
        }
    }
    return true; // 添加返回值以确保函数返回
}

很难判断哪种方法是“更好”的,因为取消本身实际上是依赖于场景的,并且超出了语言核心,但目前我接受第二种方法,因为它适合我的基于协程的运行时。例如,想象一下你正在等待 Redis 服务器的命令,给这个操作的取消提供一个好的定义是很困难的,因为 TCP 数据包可能已经到达服务器端。因此,总而言之,借助 C++20 协程模型,现在你可以实现很多功能,但在异步编程中我们仍然有许多未解的问题。我的代码库如果你感兴趣。

博主点评: C++20 协程为异步编程带来了革命性的改进,简化了代码结构与可读性,但在复杂的取消逻辑方面仍需进一步探索。合理的设计和实现将是未来发展的关键。

原文链接: https://www.reddit.com/r/cpp/comments/1u1y6ep/how_far_can_c20_coroutines_go_in_asynchronous/

[h] 返回首页