インテル® TBB 2020 の有用な機能: task_arena クラス

この記事は、インテル® デベロッパー・ゾーンに公開されている『Intel® Threading Building Blocks Documentation』の「task_arena Class」の日本語参考訳です。


概要

これは明示的なユーザー管理タスクのスケジューラー・アリーナ、および現在のアリーナに適用可能な関数の名前空間を表すクラスです。

構文

class task_arena;

ヘッダー

#include "tbb/task_arena.h"

説明

task_arena クラスは、スレッドがタスクを共有および実行可能な場所を示します。

task_arena のタスクを同時に実行するスレッド数は並行レベルにより制限されます。並列レベルはその領域にのみ影響し、それ以前の task_scheduler_init の影響を受けません。

スケジューラーが使用するスレッド数の合計は、マシンのデフォルトのスレッド数または最初のタスク・スケジューラーの初期化で指定された値のいずれか大きい方に制限されます。そのため、task_arena に割り当てられるスレッド数は、並行レベルに関係なく、最大値を超えることはありません。さらに、task_arena は、許可されたスレッドの最大数より小さい場合でも、指定されたスレッド数を取得しないことがあります。

タスク・スケジューラーを明示的または暗黙的に初期化する各ユーザースレッドは、暗黙の内部タスク・アリーナ・オブジェクトを作成して使用します。あるアリーナにスポーンまたは追加されたタスクは、ほかのアリーナでは実行できません。

task_arena インスタンスも内部表現への参照を保持しますが、存続期間を完全に制御できるわけではありません。内部表現は、タスクを保持したりほかのスレッドが参照している間は破棄できません。

task_arena コンストラクターは、内部アリーナ・オブジェクトを作成しません。コンストラクターをアタッチするときにすでに存在する可能性があります。そうでない場合は、明示的に task_arena::initialize を呼び出して作成されるか、初回使用時に遅れて作成されます。

インテル® スレッディング・ビルディング・ブロック (インテルインテル® TBB) は、task_arena で示される明示的なアリーナ・オブジェクトを処理する場合、現在のスレッドに対して暗黙のアリーナ・オブジェクトを割り当てません。しかし、同じスレッドの後続のスケジューラー初期化では、task_scheduler_init で指定されたスレッド数に関係なくデフォルトの設定が使用されます。これは、タスク・スケジューラーに関連するメソッドの呼び出しが、暗黙のアリーナ・オブジェクトの作成を含め、現在のスレッドのスケジューラーを初期化する以前のバージョンのインテル® TBB との互換性を提供します。

名前空間 this_task_arena には、呼び出し元のスレッドで使用されているアリーナ (明示的な task_arena または暗黙のアリーナ・オブジェクト) との対話を可能にするグローバル関数が含まれます。

メンバー

namespace tbb {
    class task_arena {
    public:
        static const int automatic = implementation-defined;
        static const int not_initialized = implementation-defined;
        struct attach {};

        task_arena(int max_concurrency = automatic, unsigned reserved_for_masters = 1);
        task_arena(const task_arena &s);
        explicit task_arena(task_arena::attach);
        ~task_arena();

        void initialize();
        void initialize(int max_concurrency, unsigned reserved_for_masters = 1);
        void initialize(task_arena::attach);
        void terminate();

        bool is_active() const;
        int max_concurrency() const;

        // C++11 までサポート
        template<typename F> void execute(F& f);
        template<typename F> void execute(const F& f);
        template<typename F> void enqueue(const F& f);
        template<typename F> void enqueue(const F& f, priority_t p);

        // C++11 以降でサポート
        template<typename F> auto execute(F& f) -> decltype(f());
        template<typename F> auto execute(const F& f) -> decltype(f());
        template<typename F> void enqueue(F&& f);
        template<typename F> void enqueue(F&& f, priority_t p);

        static int current_thread_index(); // 非推奨
    };
    namespace this_task_arena {
        int current_thread_index();
        int max_concurrency();

        // C++11 までサポート
        template<typename F> void isolate(F& f);
        template<typename F> void isolate(const F& f);

        // C++11 以降でサポート
        template<typename F> auto isolate(F& f) -> decltype(f());
        template<typename F> auto isolate(const F& f) -> decltype(f());
    }
}

次の例では、2 つの parallel_for ループを同時に実行します。一方はスケーラブルですが、他方はそうではありません。スケーラブルでないループは最大 2 スレッドに制限されるため、ほとんどのスレッドはスケーラブルなループに使用できます。task_group を使用して、タスクの特定のサブセットを待機します。

tbb::task_scheduler_init def_init; // デフォルトのスレッド数を使用
tbb::task_arena limited(2);        // このアリーナでは 2 スレッド以下
tbb::task_group tg;

limited.execute([&]{ // このジョブには最大 2 スレッドを使用
    tg.run([]{ // タスクグループ内で実行
        tbb::parallel_for(1, N, unscalable_work());
    });
});

// 上記のループと同時に次のジョブを実行
// デフォルトのスレッド数まで使用できます
tbb::parallel_for(1, M, scalable_work());

// 制限されたアリーナでのタスクグループの完了を待機します
limited.execute([&]{ tg.wait(); });

次の表は、task_arena メンバーに関する追加情報を示します。

メンバー 説明
task_arena(int max_concurrency = automatic, unsigned reserved_for_masters = 1) 特定の同時実行制限 (max_concurrency) で task_arena を作成します。制限の一部は、reserved_for_masters を使用してアプリケーション・スレッド用に予約できます。予約量は、制限を超えることはできません。

インテル® TBB 4.4 Update 0 以前は、reserved_for_masters の有効な値は 0 および 1 のみでした。

注意

ここで、max_concurrency および reserved_for_masters が 1 以上に設定されると、インテル® TBB のワーカースレッドはアリーナに参加しません。その結果、これらのアリーナではキューに入れられたタスクの実行は保証されません。task_arena::enqueue() および task::enqueue() は、ワーカースレッドを持たないアリーナで使用してはなりません。

static const int automatic

一部のアリーナ・パラメーターの基本値。パラメーターがこの値に設定されると、システム構成に基づいて自動的に定義されます。

static const int not_initialized

メソッドまたは関数から返された場合、アクティブなアリーナがないか、アリーナ・オブジェクトがまだ初期化されていないことを示します。
task_arena(const task_arena&) 別の task_arena インスタンスから設定をコピーします。
explicit task_arena(task_arena::attach) 呼び出しスレッドが使用している内部アリーナに接続する task_arena インスタンスを作成します。アリーナがまだ存在しない場合、デフォルト・パラメーターで task_arena を作成します。

ほかのコンストラクターとは異なり、このコンストラクターは既存のアリーナに接続するときに新しい task_arena を自動的に初期化します。

~task_arena() 内部アリーナ表現への参照を削除し、task_arena インスタンスを破棄します。ほかのメソッドを同時に呼び出した場合、スレッドセーフではありません。

void initialize()
void initialize(int max_concurrency, unsigned reserved_for_masters = 1)
void initialize(task_arena::attach)

内部アリーナ表現の初期化を実行します。引数が指定されている場合、以前のアリーナ・パラメーターをオーバーライドします。クラス task_arena::attach のインスタンスが引数として指定され、呼び出しスレッドが使用する内部アリーナが存在する場合、メソッドはアリーナ・パラメーターを無視して task_arena をその内部アリーナに接続します。すでに初期化されているアリーナに対して呼び出された場合、メソッドは効果がありません。

initialize の呼び出し後、アリーナのパラメーターは固定され変更することはできません。

void terminate() task_arena オブジェクトを破棄せずに内部アリーナ表現への参照を削除します。task_arena オブジェクトは再利用できます。ほかのメソッドを同時に呼び出した場合、スレッドセーフではありません。

bool is_active() const アリーナが初期化されている場合、true を返します。初期化されていなければ、false を返します。
int max_concurrency() const アリーナの並行レベルを返します。アリーナは初期化されている必要がなく、初期化を実行しません。

C++11 まで: template void enqueue(const F& f)

C++11 以降: template void enqueue(F&& f)

タスクをアリーナのキューに投入し、指定されたファンクター (タスクにコピーまたは移動) を処理して、直ちにリターンします。

このメソッドでは、呼び出しスレッドはアリーナに参加する必要はありません。つまり、アリーナ外の任意の数のスレッドが、ブロックすることなくアリーナにワークを送信できます。

注意

アリーナのキューに投入されたタスクが、ほかのタスクと同時に実行される保証はありません。

注意

例外がスローされ、ファンクターでキャッチされない場合の動作は未定義です。

C++11 まで: template void enqueue(const F& f, priority_t)

C++11 以降: template void enqueue(F&& f, priority_t)

指定された優先度でタスクを追加します。他のすべての点において、enqueue(f) と類似しています。

C++11 まで:
template void execute(F&)
template void execute(const F&)

C++11 以降:
template auto execute(F&) -> decltype(f())
template auto execute(const F&) -> decltype(f())

アリーナで指定されたファンクターを実行します。C++ 11 以降、関数はファンクターによって返された値を返します。
 

呼び出しスレッドは、可能であればアリーナに参加してファンクターを実行します。リターンすると、以前のタスク・スケジューラーの状態と浮動小数点設定を復元します。

アリーナへジョインできない場合、ファンクターをタスクにラップしてアリーナのキューに追加し、OS カーネルの同期オブジェクトを使用してジョインする機会を待ち、タスクの完了後に終了します。

注意

アリーナ (B) に間接的に 2 回ジョインすると (B.execute([]{ A.execute([]{ B.execute(f); }); });); のように)、アリーナの並行性が低下するため、デッドロックが発生する可能性があります。

アリーナ外の任意の数のスレッドがアリーナにワークを送信できますがブロックされる可能性があります。ただし、ワークを実行できるのは、アリーナで指定されている最大スレッド数までです。

execute は、ワーカースレッドのアリーナへの要求を減らし、ワーカーを退出させることで呼び出しスレッドのスロットを解放できます。

ファンクターでスローされた例外はキャプチャーされ、execute から再スローされます。正確な例外伝播が利用できないか無効な場合、例外は同じスレッドであっても tbb::captured_exception にラップされます。

static int current_thread_index() 非推奨です。これは、this_task_arena::current_thread_index() (以下を参照) と等価です。ただし、呼び出しスレッドがタスク・スケジューラーを初期化していない場合は、-1 を返します。

次の表は、名前空間 this_task_arena のメンバーに関連する情報を示します。

メンバー 説明
int current_thread_index() 呼び出しスレッドが現在使用しているタスクアリーナのスレッド・インデックスを返します。スレッドがタスク・スケジューラーを初期化していない場合は、task_arena::not_initialized を返します。

スレッド・インデックスは、0 からアリーナの並行レベルまでの整数です。スレッド・インデックスは、アリーナにジョインするアプリケーション (マスター) スレッドとワーカースレッドの両方に割り当てられ、アリーナを終了するまで保持されます。アリーナを共有するスレッドのインデックスは一意です。つまり、アリーナ内の 2 つのスレッドが同じインデックスを持つことはありませんが、必ずしも連続していません。

タスクを実行しないスレッドは、いつでもアリーナを離れることがあるため、スレッドのインデックスは、同じタスクグループやアルゴリズムに属している 2 つのタスク間でも変わることがあります。

異なるアリーナを使用するスレッドは、同じインデックス値を持つことがあります。

execute() で入れ子になったアリーナにジョインすると、リターン時に復元される外部アリーナのインデックスは保持されますが、現在のインデックス値が変更されることがあります。

これは、task_scheduler_observer がアリーナに入るスレッドを特定のハードウェアに固定するために使用できます。

int max_concurrency() 呼び出しスレッドが現在使用しているタスクアリーナの同時実行レベルを返します。スレッドがタスク・スケジューラーを初期化していない場合は、ハードウェア構成に応じて自動的に決定された同時実行レベルを返します。

C++11 まで:
template void isolate(F&)
template void isolate(const F&)

C++11 以降:
template auto isolate(F&) -> decltype(f())
template auto isolate(const F&) -> decltype(f())

呼び出しスレッドをファンクターのスコープでスケジュールされたタスクだけを処理するように制限することで、指定されたファンクターを分離して実行します (分離領域とも呼ばれます)。C++ 11 以降、関数はファンクターによって返された値を返します。

注意

ファンクターによって返されたオブジェクトは参照できません。代わりに、std::reference_wrapper を使用します。

注意

フローグラフや task_group などの非同期並列構造は、分離した領域内で注意して使用する必要があります。graph::wait_for_all または task_group::wait が分離して実行される場合、フローグラフ・ノードに対して try_put を呼び出すか、task_group::run を呼び出してスケジュールされたタスクは、同じ分離領域またはそれ以前にその領域で生成されたタスクでスケジュールされている場合にのみアクセスできます。そうでない場合、パフォーマンスの問題が生じたり、デッドロックが発生する可能性があります。

上位トピック: タスク・スケジューラー (英語)

関連情報

タスクグループ (英語)
task_scheduler_init クラス (非推奨) (英語)
task_scheduler_observer クラス (英語)
例外 (英語)
浮動小数点設定 (英語)

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事