ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

oneTBB Flow Graph 保留式 join_node(Reservation)协议解析与实战

oneTBB Flow Graph 保留式 join_node(Reservation)协议解析与实战 oneTBB Flow Graph 保留式 join_nodeReservation协议解析与实战【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文以 mold 项目所携带的 oneTBBThreading Building Blocks第三方库为背景深入讲解 Flow Graph 中join_node的reserving保留式策略它如何在没有内部缓冲的情况下通过try_reserve/try_consume/try_release三阶段协议在多个输入端口之间安全地凑齐一条输出消息。读完本文你将掌握 reserving 与 queueing 等策略的本质区别、保留式 join 对前驱节点的可保留reservable要求、其推/拉push/pull模式切换的完整时序以及一个可实际运行的示例程序与源码级实现印证。该文档位于 Flow_Graph_Reservation.rst对应的核心实现位于 _flow_graph_join_impl.h 与 _flow_graph_cache_impl.h。join_node 的四种策略与 reserving 的定位oneTBB Flow Graph 中的join_node是一个多输入、单输出的汇聚节点它需要每个输入端口上都至少有一条消息才能组合出一条输出消息。根据官方用户指南|full_name| flow graph的描述join_node共有四种可选策略policy策略特点queueing每个输入端口带内部缓冲消息先到先存凑齐后拼接输出reserving无内部缓冲采用先保留、后消费/释放的两阶段协议key_matching按键key匹配不同输入端口的消息tag_matching按标签tag匹配消息key_matching 的别名/泛化形式其中reserving策略的核心约束是它没有内部缓冲也不会主动从输入端拉取消息直到每个输入端口都有可用的前驱节点为止。要生成一条输出消息它会在每个输入端口上暂时保留temporarily reserve一条消息并且只有所有输入端口都成功完成保留时才会真正构造输出消息一旦任何一个输入端口保留失败join_node就一条消息都不会拉取。从源码层面看策略通过模板参数JP选择flow_graph.h 中分别特化了join_nodeOutputTuple, reserving和join_nodeOutputTuple, queueing前者派生自以reserving_port为端口的unfolded_join_node且构造时注册为FLOW_JOIN_NODE_RESERVING类型用于 Flow Graph 追踪工具。而保留式端口reserving_port的完整定义位于 _flow_graph_join_impl.h。保留Reservation协议的工作原理为了支撑reserving策略的join_node其前驱节点必须支持**输出保留reservation**能力。文档给出的协议流程如下推送被拒、边切换为拉取模式当连接在保留式join_node上的节点处于 push推送状态并试图推送消息时join_node总是拒绝该推送于是这条边edge/arc被切换为 pull拉取模式。逐边尝试保留保留式输入端口对每条处于 pull 状态的边调用try_reserve。如果某个前驱保留失败该输入端口会把这这条边切回 push 状态然后继续尝试下一条处于 pull 状态的边。当前驱处于已保留reserved状态时其他任何节点都不能取走这个被保留的值——保留具备独占性。全部成功则构造并推送如果每个输入端口都成功保留了一条处于 pull 状态的边join_node就用这些被保留的消息构造输出消息并尝试把它推送给所有后继节点。成功则消费如果消息成功推送给后继join_node通过调用try_consume()通知之前被保留的前驱消息已使用前驱随即丢弃这些已被成功推送的消息。失败则释放如果消息未能推送给任何后继join_node通过调用try_release()通知前驱消息未被使用。此时这些消息重新变得可被推送或被其他节点拉取。之所以reserving策略可行是因为join_node只会在每个输入端口至少有一条处于 pull 状态的边时才尝试推送并且只会在所有输入端口都成功保留消息时才构造并推送消息——因此每个输入端口的所有前驱节点中至少有一个必须是可保留reservable的。换句话说如果某个输入端口连接的全是不可保留节点保留式join_node将永远无法产生输出。源码中的三阶段印证上述协议的每一步都能在源码中找到对应实现reserving_port的操作枚举_flow_graph_join_impl.h 定义了五种操作reg_pred注册前驱、rem_pred移除前驱、res_item保留消息、rel_res释放保留、con_res消费保留并通过 aggregator 串行化处理保证并发安全。res_item处理调用my_predecessors.try_reserve(...)成功则置reserved true并返回SUCCEEDED失败则返回FAILED见同文件 L329-L353。rel_res/con_res处理分别调用my_predecessors.try_release()与my_predecessors.try_consume()随后清除reserved标志L355-L364。递归保留join_helperN::reserve从第 N-1 个输入开始逐个端口调用reserve()中途任何端口失败都会通过release_my_reservation回滚此前已成功保留的端口_flow_graph_join_impl.h这正是要么全部保留成功、要么全部不保留原子语义的实现。可保留前驱缓存reserving_port使用reservable_predecessor_cacheT, null_mutex_flow_graph_cache_impl.h。其try_reserve弹出候选前驱并调用pred-try_reserve(v)失败则把边归还register_successor继续尝试下一个try_release与try_consume都作用于当前被保留的前驱reserved_src完成后将其置空。什么节点是可保留的文档明确指出示例中两类节点的行为差异正是理解协议的关键buffer_node缓冲其输出因此接受其输出边从 push 切换到 pull 模式也支持try_get()与try_reserve()——是典型的可保留前驱broadcast_node不缓冲消息且不支持try_get()或try_reserve()——不可保留。⚠️警告CAUTION任何不支持保留的节点在连接到保留式join_node时都无法正常工作。文档中的示例程序正是用来演示这一现象的将不可保留节点连接到需要保留能力的节点是不被推荐的做法。实战示例一个保留式 join_node 的完整时序文档给出了完整的可运行示例run_example2()对应插图Flow_Graph_Reservation.xml下面逐段给出并配上执行过程分析。示例代码void run_example2() { // example for Flow_Graph_Reservation.xml graph g; broadcast_nodeint bn(g); buffer_nodeint buf1(g); buffer_nodeint buf2(g); typedef join_nodetupleint,int, reserving join_type; join_type jn(g); buffer_nodejoin_type::output_type buf_out(g); join_type::output_type tuple_out; int icnt; // join_node predecessors are both reservable buffer_nodes make_edge(buf1,input_port0(jn)); make_edge(bn,input_port0(jn)); // attach a broadcast_node make_edge(buf2,input_port1(jn)); make_edge(jn, buf_out); bn.try_put(2); buf1.try_put(3); buf2.try_put(4); buf2.try_put(7); g.wait_for_all(); while (buf_out.try_get(tuple_out)) { printf(join_node output (%d,%d)\n,get0(tuple_out), get1(tuple_out) ); } if(buf1.try_get(icnt)) printf(buf1 had %d\n, icnt); else printf(buf1 was empty\n); if(buf2.try_get(icnt)) printf(buf2 had %d\n, icnt); else printf(buf2 was empty\n); }拓扑结构如下保留式join_node jn的端口 0 有两个前驱——buffer_node buf1和broadcast_node bn端口 1 只有一个前驱——buffer_node buf2jn的输出连接到buffer_node buf_out。由于任务调度可能略有差异文档讨论的是一条可能的执行序列最终结果相同下面按步骤还原。步骤 1bn.try_put(2)bn尝试把 2 推送给jn。jn不接受该值因此从bn到jn的弧arc反向reverses即切换为 pull 模式。由于bn和jn都不缓冲消息消息 2 被丢弃。又因为jn的输入并未全部具备可用前驱此时buf2尚未就绪jn不采取进一步动作。对应插图flow_graph_reserve_buffers_1.png。步骤 2buf1.try_put(3)buf1尝试把 3 推送给jn。jn仍不接受弧反向。同样因为输入未全部就绪jn不做任何事。此时buf1缓冲了 3。对应插图flow_graph_reserve_buffers_2.png。步骤 3buf2.try_put(4)buf2尝试把 4 推送给jn。jn不接受弧反向。现在jn的两个输入端口都有了前驱于是一个构造并转发jn输出消息的任务task被派生spawned。假设该任务此刻尚未执行。对应插图flow_graph_reserve_buffers_3.png。步骤 4buf2.try_put(7)buf2已经没有后继因为它到jn的弧已经反向于是把值 7 存储在自身缓冲中。对应插图flow_graph_reserve_buffers_4.png。步骤 5jn的任务开始执行此前派生的任务现在运行jn开始逐端口保留jn尝试保留bn失败broadcast_node不支持try_reserve。到bn的弧切回正向push方向。jn尝试保留buf1成功图中被保留的节点以灰色标注。jn从buf1取走值 3但 3仍保留在buf1中以防jn转发输出失败时回滚。jn尝试保留buf2成功。jn取走值 4但同样仍保留在buf2中。jn构造输出消息tuple3,4。对应插图flow_graph_reserve_buffers_5.png。步骤 6jn推送成功并消费jn把消息推送给buf_out后者接受。由于推送成功jn通知buf1和buf2保留值已被使用两个缓冲随即丢弃这些值try_consume()。随后jn再次尝试保留因为从bn到jn的边处于 push 状态不再尝试从bn拉取jn尝试保留buf1失败buf1已空到buf1的弧切回正向方向jn不再采取进一步动作。对应插图flow_graph_reserve_buffers_6.png。步骤 7结束与输出图中不再有任何活动wait_for_all()完成。程序输出为join_node output (3,4) buf1 was empty buf2 had 7对应插图flow_graph_reserve_buffers_7.png。结果解读输出只有一个(3,4)虽然buf2中存有 4 和 7但jn的端口 0 只有buf1可保留buf1中只有 3因此只有 4 被配对消费7 被留在buf2中bn投入的 2 被丢弃broadcast_node不缓冲、不可保留与保留式join_node配合时消息会丢失buf1被清空buf2剩下 7——完美印证了保留成功才消费保留失败则释放的语义。以上各步骤对应仓库中的 7 张过程示意图flow_graph_reserve_buffers_1.png 至 flow_graph_reserve_buffers_7.png。使用限制与工程建议结合文档与源码使用保留式join_node时应当注意以下几点前驱必须可保留每个输入端口至少要有一个支持try_reserve()的节点如buffer_node、queue_node等带缓冲节点否则该端口永远无法提供消息join_node永不输出。不可保留节点会造成消息丢失broadcast_node等不支持保留的节点在 push 被拒后消息直接丢弃这不是 bug而是协议设计使然文档明确不推荐这种连接方式。无内部缓冲带来低内存占用与queueing策略相比reserving不缓存输入消息适合消息到达频率匹配、希望精确配对的场景但代价是配对时机依赖前驱的可保留性与调度。并发安全由 aggregator 保证reserving_port的所有操作注册/移除前驱、保留/消费/释放都通过 aggregator 串行化执行见 _flow_graph_join_impl.h因此多个线程并发向图投递消息是安全的。配对是原子性的join_helper的递归保留实现保证要么所有端口全部保留成功要么全部回滚释放不会出现部分端口被占用导致死锁或消息泄漏的中间态。结语reserving是 oneTBB Flow Graph 中精确配对、按需取用的典型策略它以无缓冲 推拉切换 保留/消费/释放三态协议换取了极低的内存开销和严格的配对语义。理解本文的协议流程与示例时序你就能在需要每个输入各取一条、凑齐才输出的场景中正确选用节点组合并避开broadcast_node这类不可保留节点带来的消息丢失陷阱。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表