一、并行策略
TBB框架本身就是一个并行的框架,那么在节点的消息处理过程中,并行的控制也就是相关的策略一定会有以下几类:
1、缓冲消息
缓冲即将收到的待处理的消息保存在队列(或类似缓存)中,然后再一条条的处理。当然,这个缓存队列仍然是有上限的。
2、丢弃消息
即接收到消息后直接丢弃,不再处理。
3、拒绝消息
即在达到指定条件后,不再接收新的消息。也可以理解为前二者的一个结合体,是一个指定的有限的队列。
在TBB的源码中定义了四个空的类:
struct rejecting { };//1
struct reserving { };
struct queueing { };//2
struct lightweight { };
template < typename Input,
typename Output = continue_msg,
graph_buffer_policy = queueing >
class function_node;
上面四个策略中,与并行有关系的只有1和2,其它两个用来做轻量任务处理和预分配内存机制处理。TBB中只对缓冲和相对受限缓冲(拒绝)感兴趣。从上面的代码可以看出,功能节点中的消息并发控制策略为缓冲队列方式。如果达到了并发策略的上限后,功能节点会把相关的消息缓存到队列中去,待并发下降后再次读取队列进行处理。
二、例程
继续看官网的例子:
int main(){
graph g;
int src_count = 0;
int number_of_objects = 0;
int max_objects = 3;
input_node< big_object * > s( g, [&]( oneapi::tbb::flow_control& fc ) -> big_object* {
if ( src_count < M ) {
big_object* v = new big_object();
++src_count;
return v;
} else {
fc.stop();
return nullptr;
}
} );
s.activate();
//注意下面的continue_msg和受限的3
function_node< big_object *, continue_msg, rejecting > f( g, 3,
[]( big_object *v ) -> continue_msg {
spin_for(1);
delete v;
return continue_msg();
} );
make_edge( s, f );
g.wait_for_all();
return 0;
}
上面使用continue_msg,表示是一个对前驱节点的依赖节点,而在功能节点function_node创建时,使用了rejecting缓冲策略并指定了受限的并发上限为3,即在收到并同时 处理三个消息时(即并发运行三个实例时),它将拒绝从前驱节点接收消息。而在本例程中,前驱的输入节点将缓存最后创建的对象并停止调用其Body对象即其实例函数。而当功能节点处理的当前实例数小于3个时,又可以从前驱节点中获取消息。所以消息的实例对象,最多有四个,即一个在输入节点中,三个在功能节点的处理中。
三、、总结
不同的节点有着不同的处理机制,而实际的应用场景千变万化。到达罗马的大路不止一条,何况还有小路。所以,正确的选择可能有很多,但如果寻找一条更适合当前环境的设计思路,这才是重点。最好不一定合适,合适其实从某种意义上说才是最好。