入れ子のフローグラフの使用

フローグラフのノード内部のアルゴリズムを入れ子にすることに加えて、フローグラフを入れ子にすることもできます。例えば、次のグラフ g には、ab の 2 つのノードがあります。ノード a はメッセージを受け取ると、内部依存関係グラフを構築して実行します。ノード b はメッセージを受け取ると、内部データフロー・グラフを構築して実行します。

	graph g;
	function_node< int, int > a( g, unlimited, []( int i ) -> int {
		graph h;
		node_t n1( h, [=]( msg_t ) { cout << "n1: " << i << "\n"; } );
		node_t n2( h, [=]( msg_t ) { cout << "n2: " << i << "\n"; } );
		node_t n3( h, [=]( msg_t ) { cout << "n3: " << i << "\n"; } );
		node_t n4( h, [=]( msg_t ) { cout << "n4: " << i << "\n"; } );
		make_edge( n1, n2 );
		make_edge( n1, n3 );
		make_edge( n2, n4 );
		make_edge( n3, n4 );
		n1.try_put(continue_msg());
		h.wait_for_all();
		return i;
	} );
	function_node< int, int > b( g, unlimited, []( int i ) -> int {
		graph h;
		function_node< int, int > m1( h, unlimited, []( int j ) -> int {
			cout << "m1: " << j << "\n";
			return j;
		} );
		function_node< int, int > m2( h, unlimited, []( int j ) -> int {
			cout << "m2: " << j << "\n";
			return j;
		} );
		function_node< int, int > m3( h, unlimited, []( int j ) -> int {
			cout << "m3: " << j << "\n";
			return j;
		} );
		function_node< int, int > m4( h, unlimited, []( int j ) -> int {
			cout << "m4: " << j << "\n";
			return j;
		} );
		make_edge( m1, m2 );
		make_edge( m1, m3 );
		make_edge( m2, m4 );
		make_edge( m3, m4 );
		m1.try_put(i);
		h.wait_for_all();
		return i;
	} );
	make_edge( a, b );
	for ( int i = 0; i < 3; ++i ) {
		a.try_put(i);
	}
	g.wait_for_all();

入れ子のグラフがノードの呼び出し間でその構造が変わらない場合、毎回グラフを構築することは冗長です。グラフの再構築は実行のオーバーヘッドを増やすだけです。例えば、ノード b が呼び出し間で同じグラフを再利用するように上記の例を変更できます。

	graph h;
	function_node< int, int > m1( h, unlimited, []( int j ) -> int {
		cout << "m1: " << j << "\n";
		return j;
	} );
	function_node< int, int > m2( h, unlimited, []( int j ) -> int {
		cout << "m2: " << j << "\n";
		return j;
	} );
	function_node< int, int > m3( h, unlimited, []( int j ) -> int {
		cout << "m3: " << j << "\n";
		return j;
	} );
	function_node< int, int > m4( h, unlimited, []( int j ) -> int {
		cout << "m4: " << j << "\n";
		return j;
	} );
	make_edge( m1, m2 );
	make_edge( m1, m3 );
	make_edge( m2, m4 );
	make_edge( m3, m4 );

	graph g;
	function_node< int, int > a( g, unlimited, []( int i ) -> int {
		graph h;
		node_t n1( h, [=]( msg_t ) { cout << "n1: " << i << "\n"; } );
		node_t n2( h, [=]( msg_t ) { cout << "n2: " << i << "\n"; } );
		node_t n3( h, [=]( msg_t ) { cout << "n3: " << i << "\n"; } );
		node_t n4( h, [=]( msg_t ) { cout << "n4: " << i << "\n"; } );
		make_edge( n1, n2 );
		make_edge( n1, n3 );
		make_edge( n2, n4 );
		make_edge( n3, n4 );
		n1.try_put(continue_msg());
		h.wait_for_all();
		return i;
	} );
	function_node< int, int > b( g, unlimited, [&]( int i ) -> int {
		m1.try_put(i);
		h.wait_for_all(); // h は破棄されないため、これはオプション
		return i;
	} );
	make_edge( a, b );
	for ( int i = 0; i < 3; ++i ) {
		a.try_put(i);
	}
	g.wait_for_all();

内部グラフが完了するまで b のボディーをブロックする場合は、変更後のコードで b のボディーの呼び出しの最後に h.wait_for_all() を呼び出す必要があります。b の最初の実装では、グラフがスコープの最後で破棄されていたため、各呼び出しの最後に h.wait_for_all を呼び出す必要がありました。上記の b のボディーでは、m1.try_put(i) を呼び出した後、h がアイドル状態になるのを待つことなくリターンできます。

関連情報