ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

mold 项目中的 oneTBB Flow Graph:异常处理与取消实战指南

2026/9/15 16:08:26 拓冰建站 浏览量
mold 项目中的 oneTBB Flow Graph:异常处理与取消实战指南 mold 项目中的 oneTBB Flow Graph异常处理与取消实战指南【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本指南围绕 mold 仓库所捆绑的 oneTBBThreading Building Blocks第三方库中的 Flow Graph 组件系统讲解图执行过程中的异常传播、显式取消、图重置以及嵌套并行取消四大机制。结合 Flow-Graph-exception-tips.rst 文档与 _flow_graph_impl.h 源码你将掌握如何在节点体内捕获异常使图继续执行、如何让异常在wait_for_all()调用点被重新抛出、如何不借助异常而主动取消一张图、如何用graph::reset()让被取消的图重新可执行以及如何控制嵌套并行与父图之间的取消联动关系。说明本文所述的 Flow Graph 位于 mold 仓库的 third-party/tbb 目录是 mold 为链接过程并行化所捆绑的 oneTBB 代码库。其官方用户指南tbb_userguide存放在 third-party/tbb/doc/main/tbb_userguide 下本指南即围绕其中的 Flow Graph 异常与取消专题展开。一、异常与取消Flow Graph 的两条执行中断路径Flow Graph 的图执行可以被直接取消也可以因为异常传播出节点 body而被取消取消之后你可以选择重置图以便重新执行。这是 Flow-Graph-exception-tips.rst 开篇给出的核心模型整个专题由四个相互衔接的子主题构成子主题文档解决的核心问题节点内捕获异常catching_exceptions.rst异常在节点 body 内部被捕获图继续正常运行显式取消图cancel_a_graph.rst不抛异常通过task_group_context::cancel_group_execution()主动终止图重置被取消的图use_graph_reset.rst取消后图处于不确定状态用graph::reset()恢复可执行性取消嵌套并行cancelling_nested_parallelism.rst图节点内嵌套的并行算法/子图是否随父图一起取消在 oneTBB 的通用算法层面如parallel_for异常处理遵循三步流程见 Exceptions_and_Cancellation.rst捕获异常 → 取消算法未开始的迭代不再执行→ 在调用算法的线程上抛出异常。Flow Graph 的异常与取消语义正是这套通用机制在图结构上的具体化——异常不再只作用于一个算法而是波及整张图的所有节点。二、在抛出异常的节点内捕获异常图继续执行当节点 body 内部捕获并处理了异常时图的执行不受任何影响与普通函数调用中捕获异常的直觉完全一致。2.1 未捕获异常导致整图取消考虑如下三节点链式图f1 → f2 → f3其中f2的 body 直接抛出异常而未捕获graph g; function_nodeint, int f1(g, 1, [](int i) { return i; }); function_nodeint, int f2(g, 1, [](const int i) - int { throw i; return i; }); function_nodeint, int f3(g, 1, [](int i) { return i; }); make_edge(f1, f2); make_edge(f2, f3); f1.try_put(1); f1.try_put(2); g.wait_for_all();这段代码的执行结果f2抛出的异常在传播出节点 body 时未被捕获因此整张图所有节点的执行都被取消异常在g.wait_for_all()的调用点被重新抛出由于该异常在main中也没有被处理程序将直接终止terminate。从 _flow_graph_impl.h 的graph::wait_for_all()源码可以看出这套机制的底层实现等待图空闲后检查my_context-is_group_execution_cancelled()得到cancelled标志若等待过程抛出异常则在on_exception回调中将caught_exception与cancelled同时置为true并在返回前执行my_context-reset()清理上下文状态。2.2 在节点体内捕获图不受影响如果希望在节点内部消化异常、让图继续运转把捕获逻辑写进 body 即可function_nodeint, int f2(g, 1, [](const int i) - int { try { throw i; } catch (int j) { cout Caught j \n; } return i; });此时f2对每个输入都正常返回f1 → f2 → f3的整个数据流不受任何干扰——这就是在抛出异常的节点内捕获的推荐实践适合异常属于节点局部业务、不影响全局执行语义的场景。2.3 在wait_for_all()调用点捕获图被取消第三种选择是在调用wait_for_all()的地方捕获异常try { g.wait_for_all(); } catch (int j) { cout Caught j \n; }这种情况下图的执行已被取消。以 2.1 的图为例具体后果是输入1永远到不了f3f2抛出后下游f3不再执行输入2永远到不了f2和f3取消后尚未开始的节点不再处理任何消息。这正是文档强调的异常传播出节点 body ⇒ 全部节点取消语义也为下一节的图重置埋下伏笔。三、显式取消一张图不抛异常的cancel_group_execution()并非所有取消都源于异常。你可以通过显式task_group_context来主动取消图执行从而避免取消必须伴随异常的限制。3.1 外部线程显式取消task_group_context t; graph g(t); function_nodeint, int f1(g, 1, [](int i) { return i; }); function_nodeint, int f2(g, 1, [](const int i) - int { cout Begin i \n; spin_for(0.2); cout End i \n; return i; }); function_nodeint, int f3(g, 1, [](int i) { return i; }); make_edge(f1, f2); make_edge(f2, f3); f1.try_put(1); f1.try_put(2); spin_for(0.1); t.cancel_group_execution(); g.wait_for_all();关键行为文档明确给出已经开始的节点会执行完毕f2会为输入1完整打印Begin 1与End 1因为取消发生时它已在运行尚未开始的节点不再启动f2不会收到输入2更不会执行它。这正是 Flow Graph 取消的优雅停靠特性——不打断正在进行的计算只是拒绝启动新的计算。cancel_group_execution()的具体语义可参考 Cancellation_Without_An_Exception.rst它会取消该task_group_context中的所有任务方法返回true表示本次调用确实触发了取消返回false表示该 context 此前已被取消。3.2 从节点内部取消所属图你可以在节点 body 内获取当前任务所属的task_group_context并用它取消整张图graph g; function_nodeint, int f1(g, 1, [](int i) { return i; }); function_nodeint, int f2(g, 1, [](const int i) - int { cout Begin i \n; spin_for(0.2); cout End i \n; task::self().group()-cancel_group_execution(); return i; }); function_nodeint, int f3(g, 1, [](int i) { return i; }); make_edge(f1, f2); make_edge(f2, f3); f1.try_put(1); f1.try_put(2); g.wait_for_all();task::self().group()返回当前正在执行任务的task_group_context*。文档特别强调即使图在构造时没有显式传入task_group_context也能从节点 body 中取到它——因为每个图在构造时都会创建自己的 context默认是隔离上下文详见第五节。这一模式适合发现无解条件即主动终止整张图的自顶向下取消。四、重置被取消的图graph::reset()与reset_flags4.1 为什么必须 reset无论取消来自未处理异常还是显式cancel_group_execution()被取消的图及其节点都可能处于不确定状态indeterminate state数据可能残留在节点缓冲区中例如 3.1 例子中的输入2图执行过程中的各种优化可能让节点与边处于非初始状态。因此要想重新执行/重启一张图必须先重置它。reset 的正确姿势是在捕获取消之后、重新投放消息之前调用try { g.wait_for_all(); } catch (int j) { cout Caught j \n; // do something to fix the problem g.reset(); f1.try_put(1); f1.try_put(2); g.wait_for_all(); }这是一个完整的异常 → 修复 → 重置 → 重跑闭环捕获异常后先修复导致问题的条件再g.reset()清空不确定状态随后重新try_put输入并再次wait_for_all()让图以全新状态再执行一轮。4.2 源码中的重置选项reset_flags从 _flow_graph_impl.h 可以看到graph::reset()支持的可选标志可组合使用// flags to modify the behavior of the graph reset(). Can be combined. enum reset_flags { rf_reset_protocol 0, rf_reset_bodies 1 0, // delete the current node body, reset to a copy of the initial node body. rf_clear_edges 1 1 // delete edges };标志值作用rf_reset_protocol0默认重置仅恢复执行协议相关的内部状态rf_reset_bodies10删除当前节点 body恢复为初始 body 的副本rf_clear_edges11删除图中所有边此外graph类还提供了配套的状态查询与取消接口源码位置void reset(reset_flags f rf_reset_protocol)线程不安全的状态重置默认仅做协议级重置void cancel()取消关联task_group_context的执行bool is_cancelled()查询图是否处于已取消状态bool exception_thrown()查询图是否因异常而中止。wait_for_all()每次调用时也会先清零cancelled与caught_exception标志见 源码这意味着图对象本身是可以反复wait_for_all → 捕获/取消 → reset → 再 wait_for_all复用的这正是长时间运行的服务中处理单轮图执行失败的标准模式。五、取消嵌套并行task_group_context的绑定关系决定一切Flow Graph 的节点 body 内常常会调用其他并行算法parallel_for、parallel_reduce等或嵌套的 Flow Graph。当外层图被取消显式或异常触发时这些嵌套并行是否也被取消cancelling_nested_parallelism.rst 给出的结论很简洁嵌套并行是否被取消取决于内层 context 是否绑定到外层 context。具体规则若嵌套算法/子图显式使用了与外层图相同的task_group_context或绑定到其上的 context则嵌套并行会随之被取消若嵌套并行使用独立/隔离的 context则不受外层取消影响会继续执行完毕默认行为如果你没有为一张 Flow Graph 显式提供task_group_context它会被创建为隔离上下文isolated context——这正是 _flow_graph_impl.h 中两个构造函数注释所印证的设计//! Constructs a graph with isolated task_group_context graph(); //! Constructs a graph with use_this_context as context explicit graph(task_group_context use_this_context);因此若要实现父图取消 ⇒ 嵌套任务一并取消必须显式构造共享的task_group_context并同时传入外层图与内层并行结构即 3.1 节graph g(t)的写法。若希望子任务独立于父图生命周期例如某些清理型任务必须跑完则应让内层使用自己的隔离 context。这一机制与 oneTBB 通用嵌套并行规则完全一致参见 Cancellation_and_Nested_Parallelism.rst所有嵌套并行的取消关系都由显式task_group_context对象控制未显式提供 context 的嵌套算法默认处于隔离状态。六、实践要点与决策速查综合文档与源码可以归纳出 Flow Graph 异常处理与取消的决策模型异常是节点局部业务问题→ 在 body 内try/catch图不受影响数据流继续异常需要中止整张图→ 让异常传播出 body在wait_for_all()处捕获注意此时图已被取消、缓冲区可能有残留输入需要无异常地中止整张图→ 显式task_group_contextcancel_group_execution()或从节点内task::self().group()-cancel_group_execution()已开始的节点跑完未开始的节点不再启动需要复用被取消的图→ 捕获/处理取消后调用g.reset()必要时配合rf_reset_bodies、rf_clear_edges再重新try_put输入需要控制嵌套并行是否随父图取消→ 显式共享task_group_context则联动取消隔离 context 则各自独立默认创建隔离 context。这套机制在 mold 的 third-party/tbb 代码树中均有完整实现与文档支撑用户指南位于 third-party/tbb/doc/main/tbb_userguide核心入口 Flow-Graph-exception-tips.rst底层实现位于 _flow_graph_impl.h 的graph类与reset_flags枚举。掌握上述决策点即可在依赖 Flow Graph 构建流水线的场景中写出异常可控、取消可预期、图可复用的健壮并行代码。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考