グラフ間の通信

グラフ間の通信#

すべてのグラフノードには、コンストラクターへの引数の 1 つとしてグラフ・オブジェクトへの参照が必要です。同じグラフの一部であるノード間にのみエッジを構築するのが安全です。エッジは、ランタイム・ライブラリーに対してグラフのトポロジーを表現します。異なるグラフ内の 2 つのノードを接続すると、graph::wait_for_all の呼び出しや例外処理など、グラフ全体の操作を判断するのが難しくなる可能性があります。パフォーマンスを最適化するため、ライブラリーはユーザーが予期しないタイミングでノードの先行ノードまたは後続ノードを呼び出すことがあります。

2 つのグラフが通信する場合は、それらの間にエッジを作成せず、代わりに try_put を明示的に呼び出します。これにより、ランタイム・ライブラリーが 2 つのノードの関係を想定する必要なくなり、グラフの境界を越えるイベントを容易に推論できます。ただし、グラフ全体の操作について推論するのはまだ難しい可能性があります。例えば、次のグラフについて考えてみます。

graph g; 
function_node< int, int > n1( g, 1, [](int i) -> int { 
    cout << "n1\n"; 
    spin_for(i); 
    return i; 
} ); 
function_node< int, int > n2( g, 1, [](int i) -> int { 
    cout << "n2\n"; 
    spin_for(i); 
    return i; 
} ); 
make_edge( n1, n2 ); 

graph g2; 
function_node< int, int > m1( g2, 1, [](int i) -> int { 
    cout << "m1\n"; 
    spin_for(i); 
    return i; 
} ); 
function_node< int, int > m2( g2, 1, [&](int i) -> int { 
    cout << "m2\n"; 
    spin_for(i); 
    n1.try_put(i); 
    return i; 
} ); 
make_edge( m1, m2 ); 

m1.try_put( 1 ); 

// 次の呼び出しは直ちに返されます: 
g.wait_for_all(); 
// 次の呼び出しは、m1 と m2 の後に返されます
g2.wait_for_all();
 
// 両方のグラフで wait_for_all が呼び出されたにもかかわらず、 
// n1 と n2 が終了する前にここに到達します

上記の例では、m1.try_put(1) はノード m1 にメッセージを送信し、ノード m1 はそのボディーを実行してからノード m2 にメッセージを送信します。次に、ノード m2 は本体を実行し、明示的な try_put を使用して n1 にメッセージを送信します。次に、n1 はそのボディーを実行し、n2 にメッセージを送信します。ランタイム・ライブラリーには、エッジが存在しないため、m2n1 の先行とは見なしません。

これらのグラフによって生成されたすべてのタスクの完了を待機する場合、両方のグラフで wait_for_all 関数を呼び出す必要があります。ただし、グラフ間の通信があるため、呼び出し順序が重要になります。上記の (誤った) コードセグメントでは、g にはまだアクティブなタスクがないため、g.wait_for_all() への最初の呼び出しはすぐに戻り、その時点で生成されたタスクのみが g2 に属します。g2.wait_for_all の呼び出しでは、m1m2g2 に属しているため、両方の完了後に戻ります。ただし、n1n2g に属しているため、呼び出しは待機しません。そのため、n1n2 が完了する前に、このコードセグメントの終わりに到達します。

wait_for_all の呼び出しを入れ替えると、コードは期待どおりに動作します。

g2.wait_for_all(); 
g.wait_for_all(); // すべてのタスクが完了

これら 2 つの小さなグラフがどのように相互作用するかを推論することは、それほど難しくありませんが、サイクルを伴う 2 つの大きなグラフの相互作用は理解するのは困難です。したがって、異なるグラフ内のノード間通信は、注意して行う必要があります。