TBB中的join节点的Reservation

一、join_node的策略

join_node的策略有四种方式,即queueing、reserving、key_matching 和 tag_matching。其实这个好理解,可以结合节点类型中的缓冲节点和它进行对比。在前面分析过,join_node就是一个多对一的处理节点,它最重要的方式就是从多个输入中组成一个元组的消息然后将其传输到输出节点。
但join_node有一个需要注意的特点,即如果它无法从所有的输入端口获取消息,那么就不会创建输出消息。这个非常重要,因为在TBB中,一个端口可以连接N个节点,所以这句话的意思,只要这个节点的输入端口都至少有一个消息时,输出消息就会被创建。
可以从两个层次来理解这个问题,join_node节点是一个多对一的功能节点,而节点内的输入端口也是一个多对一的端口。然后,join_node会临时在每个输入端口保留一条消息,当每个输入端口的消息不小于一条时,输出消息即被创建。
这意味着,需要输入端口对应的输入节点(前继节点)具有保留消息的功能(这就是说,不具有这个功能的节点无法在join_node中正常工作)。

二、Reservation的工作方式

看一下官网的说明:
1、When a node connected to a reserving join_node in push state tries to push a message, the join_node always rejects the push and the edge connecting the nodes is switched to pull mode.
2、The reserving input port calls try_reserve on each edge in pull state. This may fail; if so, the reserving input port switches that edge to push state, and tries to reserve the next node connected by an edge in pull state. While the input port’s predecessor is in reserved state, no other node can retrieve the reserved value.
3、If each input port successfully reserves an edge in pull state, the reserving join_node will create a message using the reserved messages and try to push the resulting message to any nodes connected to it.
4、If the message is successfully pushed to a successor, the predecessors that were reserved are signaled that the messages were used (by calling try_consume().) Those messages will be discarded by the predecessor nodes, because they have been successfully pushed.
5、If the message was not successfully pushed to any successor, the predecessors that were reserved are signaled that the messages were not used (by calling try_release().) At this point, the messages may be pushed to or pulled by other nodes.
不要被它弄晕,其实很简单。支持保留join_node的前继节点无法自己控制推拉状态而被动由join_node确定为拉状态;join_node的输入端口可以使用try_reserve,但在失败时会改为推状态并尝试保留拉取状态的下一个节点且只能它自己进行检索保留的值。如果每个输入端口都有保留的消息则会成功创建一个输出消息并在成功将其推送到输出端口对应的节点时,会通知相关输入节点可丢弃保留的此消息;否则消息可以被推送到其实节点或被其它节点拉取。

三、例程

如果上面的解释还不清晰,可以看下面的例子分析:

void run_example2() {  // example for Flow_Graph_Reservation.xml
    graph g;
    broadcast_node<int> bn(g);
    buffer_node<int> buf1(g);
    buffer_node<int> buf2(g);
    typedef join_node<tuple<int,int> reserving> join_type;
    join_type jn(g);
    buffer_node<join_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_port<0>jn));
    make_edge(bn,input_port<0>jn));      // attach a broadcast_node
    make_edge(buf2,input_port<1>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",get<0>tuple_out), get<1>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");
}

broadcast_node是不缓存或者说保留消息的,而后面的两个buffer_node是可以的。这里将广播节点bn和缓存节点buf1都连接到了join_node的输入端口0上(即上面提到的两层中的端口上的多对一),而将buf2节点连接到了join_node节点的输入端口1上。即此join_node也是一个拥有三个输入节点(两个输入端口)连接映射和一个输入端口0上有两个输入节点连接映射节点。那么它是如何运行的呢?
1、广播节点bn推送2到jn失败(看前面的方式第一条),改为拉取,但bn和jn均未缓存消息,消息被丢弃。jn不动作。
2、缓冲节点buf1将3推送到jn失败,必为拉取,但buf1缓存了3,jn仍然无动作
3、buf2逻辑同buf1,只是缓存的数据为7。
4、此时join_node节点jn的两个输入端都有消息,则生成一个任务来发送消息。
5、任务生成,jn获取bn消息,失败,改为推送
6、jn从buf1获取消息成功,接收3,此时buf1中仍然保留3
7、jn与buf2逻辑同上
8、jn构建发送消息元组(3,4)并发送到输出节点。
9、jn向buf1,buf2发送通知信号,消息已经被使用,可以丢弃。
10、jn继续拉取消息,但bn处于推送状态,故对buf1进行操作,失败。
11、jn不再动作,结束整个图的流程。

四、总结

TBB框架有很多细节需要处理,但勿需焦虑。很多的细节其实在应用的时候儿就会自然的出现,不明白再去查阅相关的文档即可。重点是掌握好其整体的逻辑流程,抓大放小,强干弱枝。还是那句话,细节很重要,但不要限入细节不可自拔。

你可能感兴趣的:(并行编程,C++11,C++,并行编程)