ネストしたフローグラフの使用

ネストしたフローグラフの使用#

フロー・グラフ・ノード内のネスト・アルゴリズムに加えて、フローグラフをネストすることも可能です。例えば、以下に 2 つのノード ab を持つグラフ g があります。ノード 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 がアイドル状態になるのを待たずに返すことが有効になります。