ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

TBB flow_graph broadcast_node 广播节点详解:语义、底层实现与实战用法(mold 第三方依赖 TBB)

TBB flow_graph broadcast_node 广播节点详解:语义、底层实现与实战用法(mold 第三方依赖 TBB) TBB flow_graph broadcast_node 广播节点详解语义、底层实现与实战用法mold 第三方依赖 TBB【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文聚焦 mold 仓库第三方依赖 Intel oneAPI TBB 中flow::broadcast_node的规范定义见 broadcast_node_cls.rst完整覆盖其接口语义、forwarding/buffering 属性、try_put/try_get行为并结合 flow_graph.h 与 broadcast_cache 的源码实现与官方测试用例说明广播推送的底层机制与典型图拓扑的搭建方法。读完本文你能够准确理解 broadcast_node 与 single-push 缓冲节点的区别、失败投递时前驱重注册机制并可直接复用官方示例构建“一对多广播”的数据流图。一、broadcast_node 是什么接口签名与三重身份broadcast_node是 TBB Flow Graph 中的一种预定义节点类型规范中的一句话定义是A node that broadcasts incoming messages to all of its successors将收到的消息广播给所有后继节点的节点。其完整类声明如下定义于头文件oneapi/tbb/flow_graph.h// Defined in header oneapi/tbb/flow_graph.h namespace oneapi { namespace tbb { namespace flow { template typename T class broadcast_node : public graph_node, public receiverT, public senderT { public: explicit broadcast_node( graph g ); broadcast_node( const broadcast_node src ); bool try_put( const T v ); bool try_get( T v ); }; } // namespace flow } // namespace tbb } //namespace oneapi从源码结构看该模板类同时继承三个基类见 flow_graph.hgraph_node使节点归属某个flow::graph具备图节点的公共设施如名称设置、复位等receiverT节点可以作为消息的接收方上游节点可以通过make_edge或try_put向其投递消息senderT节点可以作为消息的发送方向注册的后继successor推送消息。这种“收发一体”的设计正是 broadcast_node 作为中继/分发节点的体现它本身不处理消息内容只负责把输入原样复制到每一个后继。核心属性discarding broadcast-push规范明确指出broadcast_node具有discarding丢弃式和broadcast-push广播推送两种属性。结合 forwarding_and_buffering.rst 中对转发与缓冲策略的定义broadcast-push广播推送消息会向所有愿意接收它的后继推送如果没有一个后继接收成功则取决于节点自身的输出缓冲策略。与之相对的是single-push单次推送消息一旦被某个后继接收就不再向其余后继推送接收成功的消息会从节点中移除discarding丢弃式当没有任何后继接收消息时消息被直接丢弃对图的后续执行不产生任何影响。规范中的策略汇总表Buffering and Forwarding properties summary里broadcast_node所在行明确写着try_get()? noForwarding broadcast-push即它不缓存消息。这两条属性共同决定了规范正文的关键行为All messages are forwarded immediately to all successors.所有消息都会被立即转发给所有后继。作为对照仓库用户指南 broadcast_or_send.rst 明确归纳预定义节点中只有buffer_node、queue_node、priority_queue_node、sequencer_node这四类缓冲节点采用“单后继推送”single-push其余节点——包括broadcast_node——都会把消息推给所有愿意接收的后继。二、成员函数详解2.1explicit broadcast_node( graph g )构造一个属于图g的broadcast_node对象。对应源码实现flow_graph.h#L1221-L1224__TBB_NOINLINE_SYM explicit broadcast_node(graph g) : graph_node(g), my_successors(this) { fgt_node( CODEPTR(), FLOW_BROADCAST_NODE, this-my_graph, static_castreceiverinput_type *(this), static_castsenderoutput_type *(this) ); }可以看到构造函数做了两件事以this为 owner 创建内部的后继缓存my_successors类型为broadcast_cacheinput_type见 flow_graph.h#L1218以及调用fgt_node完成 profiling/调试信息注册节点类型为FLOW_BROADCAST_NODE。__TBB_NOINLINE_SYM标记表明该函数被刻意防止内联以稳定符号布局。2.2broadcast_node( const broadcast_node src )构造一个与src属于同一个图g的新节点。规范特别强调前驱列表与后继列表都不会被复制。源码实现印证了这一点flow_graph.h#L1233-L1234// Copy constructor __TBB_NOINLINE_SYM broadcast_node( const broadcast_node src ) : broadcast_node(src.my_graph) {}拷贝构造只是把新节点挂到src所在的图上边edge关系完全为空需要后续用make_edge重新建立。这对图拓扑复用是一个重要约束。另外源码中还存在一个由__TBB_PREVIEW_FLOW_GRAPH_NODE_SET宏保护的预览版构造器flow_graph.h#L1226-L1231传入node_setArgs...后会在构造的同时按顺序自动调用make_edges_in_order(nodes, *this)建边。这是预览特性使用时需开启对应宏默认不启用。2.3bool try_put( const T v )向所有后继广播v。规范的关键行为约定是Returns: always returnstrue, even if it was unable to successfully forward the message to any of its successors.即使消息没有成功转发给任何一个后继try_put也始终返回true。这一约定与 discarding 属性直接对应既然节点不缓存消息投递“失败”就不是一个可重试的状态因此返回值不携带投递结果语义调用方无需也无法据此判断哪些后继收到了消息。2.4bool try_get( T v )Returns:false.永远返回false。broadcast_node 不维护任何输入缓冲自然也没有可被拉取pull的消息——这与策略表中try_get()? no的标注完全一致。三、源码级实现broadcast_cache 如何“广播”broadcast_node 的广播行为并不直接写在类体内而是委托给私有成员my_successorsbroadcast_cacheT继承自successor_cacheT, spin_rw_mutex见 _flow_graph_cache_impl.h#L256-L419。核心流程如下3.1 后继的注册与优先级排序successor_cache::register_successor_flow_graph_cache_impl.h#L280-L286在写锁保护下把后继指针加入std::listvoid register_successor( successor_type r ) { typename mutex_type::scoped_lock l(my_mutex, true); if( r.priority() ! no_priority ) my_successors.push_front( r ); // 带优先级的后继排到队首 else my_successors.push_back( r ); // 默认后继追加到队尾 }这意味着设置了节点优先级非no_priority的后继会被插到广播列表的头部从而在广播时被最先投递未设置优先级的后继按注册顺序排在其后。broadcast_node自身对外的register_successor/remove_successor只是简单转发到该缓存flow_graph.h#L1236-L1246且恒返回true。3.2 广播投递主循环失败即降级为“前驱依赖”broadcast_cache::try_put_task_impl_flow_graph_cache_impl.h#L383-L404是真正的广播引擎graph_task* try_put_task_impl( const T t /*, const message_metainfo metainfo*/ ) { graph_task * last_task nullptr; typename mutex_type::scoped_lock l(this-my_mutex, /*write*/true); typename successors_type::iterator i this-my_successors.begin(); while ( i ! this-my_successors.end() ) { graph_task *new_task (*i)-try_put_task(t /*, metainfo*/); graph graph_ref (*i)-graph_reference(); last_task combine_tasks(graph_ref, last_task, new_task); // enqueue if necessary if(new_task) { i; // 投递成功继续下一个后继 } else { // failed if ( (*i)-register_predecessor(*this-my_owner) ) { i this-my_successors.erase(i); // 后继暂时不接收将其移出广播列表 } else { i; } } } return last_task; }从源码结构看这里有三层值得注意的机制写锁串行化整轮广播在spin_rw_mutex的写锁下完成保证一次try_put对后继列表的遍历与修改是原子的多个上游线程同时向同一个 broadcast_node 投递也不会交错破坏列表结构成功路径某后继的try_put_task返回任务指针或SUCCESSFULLY_ENQUEUED即视为该后继成功接收随后遍历下一个后继——这就是“推给所有愿意接收的后继”的实现失败路径某后继返回nullptr表示当前拒绝接收时节点会调用该后继的register_predecessor(*my_owner)把自己登记为它的前驱若登记成功返回true说明后继进入了“等待前驱”的依赖管理模式该后继随即从广播列表中擦除。此后一旦后继恢复接收能力它会通过前驱机制向broadcast_node请求后续消息从而重新进入广播列表。换言之广播列表是“动态收缩/恢复”的而不是静态的边集合。broadcast_node侧对try_put_task的封装flow_graph.h#L1248-L1268则解释了“始终返回 true”的由来若缓存返回的new_task为空会被替换为SUCCESSFULLY_ENQUEUED哨兵值返回向上游报告“已接受”这正是规范中try_put恒为true的实现基础。3.3 复位rf_clear_edges 与断言reset_nodeflow_graph.h#L1274-L1279在图复位时支持rf_clear_edges标志调用my_successors.clear()清空后继列表并用__TBB_ASSERT断言清空后列表必须为空否则报 Error resetting broadcast_node。这与graph::reset的 flags 语义参见 reset_flags_enum.rst配套允许在重用图时彻底断开拓扑。四、实战用 broadcast_node 构建“一对多、各带缓冲”的图仓库用户指南 broadcast_or_send.rst 给出了一个非常典型的场景priority_queue_node等缓冲节点是 single-push 的一条消息只会发给f1或f2其中之一若你希望f1和f2都按优先级顺序收到全部消息就需要引入broadcast_node并为每个下游 function_node 各配一个优先级队列graph g; function_node int, int, rejecting f1( g, 1, []( int i ) - int { spin_for(0.1); cout f1 consuming i \n; return i; } ); function_node int, int, rejecting f2( g, 1, []( int i ) - int { spin_for(0.2); cout f2 consuming i \n; return i; } ); priority_queue_node int q1(g); priority_queue_node int q2(g); broadcast_node int b(g); make_edge( b, q1 ); make_edge( b, q2 ); make_edge( q1, f1 ); make_edge( q2, f2 ); for ( int i 10; i 0; --i ) { b.try_put( i ); } g.wait_for_all();拓扑结构为b广播→ q1 → f1与b → q2 → f2。其设计逻辑值得逐条理解broadcast_node 本身不缓冲discarding如果直接b → f1, f2且某个 function_node 当时正忙拒绝接收消息会被丢弃。因此广播节点下游各挂一个priority_queue_node作缓冲保证广播出的每个值都被可靠持有每个function_node以rejecting策略构造第二参数1表示前驱数为 1使其不在内部缓冲转而依赖上游队列缓冲避免重复排队b.try_put(i)的返回恒为true因此循环里无需检查返回值最终g.wait_for_all()等待所有任务排空。官方指南还给出了一个反面教训若让priority_queue_node直接广播即单队列对多个拒绝式 function_node 广播当f1接收了 9 而f2拒绝时消息既不能丢f2 还没拿到也难以为继要保证全部后继都收到会引入类似垃圾回收的复杂状态跟踪这正是缓冲节点坚持 single-push、而“全量广播”需求交给 broadcast_node 多队列组合去解决的原因。五、测试用例如何验证规范行为仓库中针对该节点有两层测试可以作为行为验证的依据规范功能测试test_broadcast_node.cpp其文件头注释即写明Test for [flow_graph.broadcast_node] specification。其中test_serial_broadcaststest_broadcast_node.cpp#L87-L117用一个可计数的counting_array_receiver验证向 broadcast_node 连接 1~3 个接收者、依次投递N条消息后每个接收者对每条消息的计数恰好为 1——直接实证了“广播给所有后继、且每条消息只广播一次”的语义随后remove_edge全部边再投递一条消息并检查try_put仍返回true即使已无后继对应规范中“无后继也返回 true”的条款。ABI 一致性测试conformance_broadcast_node.cpp作为 conformance 套件的一部分用于在不同编译环境下检查broadcast_node相关符号的布局兼容性。如果你要改动或依赖 broadcast_node 的语义例如在自研节点中复用broadcast_cache以这两个测试作为回归基准最为稳妥。六、与相邻节点的选型对比在 Flow Graph 里选择“分发”方式时可以按以下要点区分依据均为本仓库规范文档与源码节点转发策略是否缓冲try_get 可用适用场景broadcast_nodebroadcast-push否中继分发同一消息要送达多个独立分支且各分支自带缓冲见 broadcast_node_cls.rstsplit_nodebroadcast-push否见 split_node_cls.rst输出多个通常是不同种类的派生值而非原样复制buffer_node/queue_node/priority_queue_node/sequencer_nodesingle-push是消息在多个消费者之间竞争消费每个消息只被消费一次join_nodebroadcast-push对输出是汇聚多路输入后再统一输出核心判别问题只有一个每条消息是“所有后继都要”broadcast选 broadcast_node还是“一个后继就够”single-push选缓冲队列类节点。这一点在 forwarding_and_buffering.rst 的策略总表和 broadcast_or_send.rst 的论证中都有明确界定。七、使用要点与限制小结消息必须可被复制投递try_put(const T v)以引用接收广播时同一消息实例会被依次送入各后继若T是昂贵类型需自行评估多次下游持有的拷贝代价不要依赖try_put返回值做失败处理它恒为true下游是否真正处理了消息只能由下游节点自身保证通常就是在其下游挂缓冲节点不要用try_get从 broadcast_node 取消息它恒为false节点无输入缓冲拷贝构造不复制边复制节点后必须重新make_edge广播列表是动态的拒绝接收的后继会被暂时移出列表并转为前驱依赖恢复后重新加入见 broadcast_cache::try_put_task_impl图复位可清边配合rf_clear_edges可清空后继列表以重用图源码中以断言保证清空彻底flow_graph.h#L1274-L1279。以上实现均位于 mold 仓库内置的 TBB 第三方源码树 third-party/tbb 中本文引用的规范文档路径为 third-party/tbb/doc/main/specification/source/flow_graph/broadcast_node_cls.rst可作为后续深入 flow_graph 其他节点如split_node、join_node阅读的直接入口。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表