フローグラフの基本: ノード

フローグラフの基本: ノード#

ノードは、oneapi::tbb::flow::graph_node から継承されるクラスであり、通常は oneapi::tbb::flow::sender<T>oneapi::tbb::flow::receiver<T>、またはその両方から継承されます。ノードは通常、受信メッセージに対して何らかの操作を実行し、0 または 1 個以上の出力メッセージを生成することがあります。一部のノードでは、複数の入力メッセージが必要になったり、複数の出力メッセージが生成されることがあります。

graph_node、sender、receiver から継承して独自のノードタイプを定義することもできますが、グラフの構築には定義済みのノードタイプを使用するのが一般的です。

function_node は、flow_graph.h で使用できる定義済みのタイプであり、1 つの入力と 1 つの出力を持つ単純な関数です。function_node のコンストラクターは 3 つの引数を受け取ります。

template< typename Body> function_node(graph &g, size_t concurrency, Body body)

パラメーター

説明

Body

ボディー・オブジェクトのタイプ。

g

ノードが属するグラフ。

concurrency

ノードの同時実行制限。同時実行制限を使用すると、1 (連続) から無制限まで、同時に実行できるノード呼び出しの数を制御できます。

body

受信メッセージに適用され、送信メッセージを生成するユーザー定義の関数オブジェクトまたはラムダ式。

以下は、単一の function_node を含む単純なグラフを作成するコードです。この例では、グラフ g に属し、2 番目の引数が 1 であるノード n が構築され、これにより、同時に発生するノードの呼び出しが最大 1 回許可されます。ボディーは、受信した各値 v を出力し、v 秒間スピンし、値を再度出力し、変更せずに v を返すラムダ式です。spin_for 関数のコードは次のようになります。

graph g; 
function_node< int, int > n( g, 1, []( int v ) -> int { 
    cout << v; 
    spin_for( v ); 
    cout << v; 
    return v; 
} );

上記の例でノードが構築された後、エッジを使用して他のノードに接続するか、関数 try_put を呼び出すことで、そのノードにメッセージを渡すことができます。エッジの使い方については次のセクションで説明します。

n.try_put( 1 ); 
n.try_put( 2 ); 
n.try_put( 3 );

次に、グラフ・オブジェクトで wait_for_all を呼び出して、メッセージが処理されるのを待機できます。

g.wait_for_all();

上記のコード例では、同時実行制限が 1 の function_node n が作成されました。メッセージシーケンス 1、2、3 を受信すると、ノード n は最初の入力 1 にボディーを適用するタスクを生成します。タスクが完了すると、ボディーを 2 に適用する別のタスクが生成されます。同様に、ノードは、そのタスクが完了するまで待機してから、3 番目のタスクを生成し、ボディーを 3 に適用します。try_put の呼び出しは、タスクが生成されるまでブロックされません。ノードがメッセージを処理するタスクをすぐに生成できない場合、メッセージはノード内にバッファリングされます。合法な場合は、同時実行制限に基づいて、次のバッファリングされたメッセージを処理するタスクが生成されます。

上記のグラフでは、各メッセージが順番に処理されます。ただし、異なる同時実行制限でノードを構築すると、並列処理できます。

function_node< int, int > n( g, oneapi::tbb::flow::unlimited, []( int v ) -> int { 
    cout << v; 
    spin_for( v ); 
    cout << v; 
    return v; 
} );

同時実行制限に unlimited を指定すると、他のタスクがいくつ生成されていても、メッセージが到着するとすぐにタスクを生成するようにライブラリーに指示できます。また、4 や 8 などの特定値で、同時実行数をそれぞれ最大 4 または 8 に制限することもできます。タスクを生成することと、スレッドを作成することの意味は異なることを覚えておくことが重要です。したがって、グラフは多くのタスクを生成する可能性がありますが、これらのタスクの実行にはライブラリーのスレッドプールで使用可能な数のスレッドだけが使用されます。

代わりに function_node コンストラクターで unlimited を使用し、ノードで try_put を呼び出すと想定します。

n.try_put( 1 ); 
n.try_put( 2 ); 
n.try_put( 3 ); 
g.wait_for_all();

ライブラリーは 3 つのタスクを生成し、各タスクは n のラムダ式をメッセージの 1 つに適用します。システム上で十分な数のスレッドが使用可能な場合、ボディーの 3 つの呼び出しはすべて並列に実行されます。ただし、システム内にスレッドが 1 つしかない場合、スレッドは順番に実行されます。