一、TBB中的节点
在TBB框架的应用中,是离不开节点这个概念的。大家可以把数据的处理、计算、中转、保存以及调度方式等理解成一个节点的抽象化。可以这样理解,TBB的运行基本是以节点为基础的。从后期的开发也可以看出,整个TBB其实是一个图(Graph)为运行框架,而节点就是图中的必须。
在稍微复杂一些算法结构中,都可以或多或少的看到图的身影,TBB中的图和离散数学或者算法中的图基本类似。所以有过这些基础的开发者应该很容易明白这个概念,同样也就很容易明白节点的概念。
二、节点类型
正因为TBB的图结构,所以其节点承担起主要的功能任务。而根据具体的功能需求不同,可以划分出不同的类型节点。下面就这些预定义节点进行一下分析说明:
1、input_node
输入节点,很容易理解,数据的输入或者起始的驱动节点。当它变成活动节点后,会产生输出动作(当然可以在输出前完成一系列的自定义动作)。它的输出方向由节点的连接控制(可以理解成图的流向)。在连接成功的前提下,输入节点和其连接节点会进行数据的交互。需要注意的是,无论是输入还是连接的节点都可以进行缓冲队列的设置,来处理节点间数据处理速度的简单不匹配。
它是一个通用的输入输出类型节点,单输出的,可以对所有的后续连接节点进行广播数据。
2、function_node
功能节点,一个单输入单输出的节点,它同样对数据进行广播转发。也可以对节点的并发和缓冲进行设置。对于每个输入只返回一个输出。
3、continue_node
它是一个单输入、单输出节点,数据进行广播发送。它的输入需要大于等于1个continue_msg类型的输入且具有通用输出类型。当接收的消息大于1时,此节点会根据输入偏移利用前置器进行continue_msg消息的处理。
4、multifunction_node
功能与function_node类似,它也有一个输入,区别在于,它有多个输出的节点。
5、broadcast_node
广播节点,单输入输出节点,如字面意义一样,把得到的数据广播给所有的连接节点,注意,它不对数据进行缓冲。
6、buffer_node, queue_node, priority_queue_node, and sequencer_node.
缓冲节点,这些类型节点都是缓冲节点。它们是单输入单输出的节点,更为需要说明的是它还是一个单播节点,即它只能发送给一个后续节点。而不是前面的广播给所有的后续连接节点。
7、join_node
组合节点,从名字就可以理解它是一个多输入单输出节点。它从多个输入节点中创建一个元组(Tuple)来处理所有的输入消息并将其广播给后续的连接节点。组合的策略可以进行设置,包括队列、持有或标签匹配等 。
8、split_node
这个和上面的正好相反,它是一个单输入多输出的节点,可以理解为对输入节点的消息进行了分裂操作。为了能够分离或者说分裂输入消息,接收的是一个元组类型,然后在不同的输出端口上输出元组的相应的元素类型消息。
9、write_once_node, overwrite_node
单输入单输出节点,缓冲并广播给后续节点。广播后,节点保留最后收到的消息,所以后续节点仍然可以继续应用它。write_once_node只接收收到的第一条消息,而后者则可以接收所有消息,将它们广播给连接的节点。同时,将保留的老数据更新为后续新数据。
10、limiter_node
限制节点,是一个多输入单输出的节点,不缓存消息,对接收的消息数据进行广播转发。它通过内部的一个流量限制器对广播的消息进行限制控制,当消息广播的数量达到阀值后则停止广播消息。可以通过特殊的输入商品来调整这个流量限制器。它的主输入和输出具有相同的数据类型。
11、indexer_node
索引器节点,多输入、单输出节点,并对收到的消息进行广播转发。它的特点在于index,输入的数据中具有标签标识(index),即数据从哪个输入节点获取。其输入消息类型为泛型类型的列表,输出类型则为tagged_msg。
12、composite_node
聚合节点,可能有0~N个输入输出端口的节点。特点就在于composite,它可以对节点的数据打包并利用元组进行数据的处理。然后再形成新的图任务。简单的理解就是,将多个图中的子任务进行聚合,形成一个新的大任务,减少任务处理的数量,提高并行的效率。
13、async_node (preview feature)
一个预览的特征,异步节点。其实就是允许图与外界节点进行通信。
当然,这些预定义的类型节点,不可能满足所有的实际场景,这就需要继承TBB库的一些基类来自定义自己的节点类型。它们一般在头文件flow_graph.h中,主要包括graph_node、类sender和类receiver。下面看一下graph_node的定义:
//_flow_graph_impl.h
//! The base of all graph nodes.
class graph_node : tbb::internal::no_copy {
friend class graph;
template<typename C, typename N>
friend class graph_iterator;
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
friend class internal::get_graph_helper;
#endif
protected:
graph& my_graph;
graph_node *next, *prev;
public:
explicit graph_node(graph& g);
virtual ~graph_node();
#if TBB_PREVIEW_FLOW_GRAPH_TRACE
virtual void set_name(const char *name) = 0;
#endif
#if TBB_DEPRECATED_FLOW_NODE_EXTRACTION
virtual void extract() = 0;
#endif
protected:
// performs the reset on an individual node.
virtual void reset_node(reset_flags f = rf_reset_protocol) = 0;
};
有过TBB节点开发经验的就会看出其基本的图节点的内容。
三、总结
在节点的应用中,要注意区分单输入(输出)、多输入(输出)与广播、单播的不同。前者指只有一个输入输出的节点,后者指的是数据转发的方式。一般来说,前者控制的是节点中控制数据流的点的数量,后者指的是连接发送的策略。
TBB的应用还是有很多细节可挖的,可惜一般的地方用不到这个框架。有兴趣的可以一起学习分析。