優先度に基づいて次に処理する作業を選択する。
インテル® スレッディング・ビルディング・ブロック (インテル® TBB) のスケジューラーは、スケーラビリティーに基づく規則を使用してタスクを選択します。 この規則は、タスクがスポーンまたはキューに入れられた順序に基づいており、タスクの内容は考慮しません。 場合によっては、優先度の関係に基づいて作業を選択するほうが良いこともあります。
複数の作業項目を指定した場合、(デフォルトのインテル® TBB の規則ではない) 次に処理する項目に対する規則があります。
プリエンプティブな優先度は必要ありません。優先度の高い項目が現れた場合、実行中の優先度の低い項目を直ちに停止する必要はありません。 プリエンプティブな優先度が必要な場合、ノンプリエンプティブなタスク処理は不適切です。 代わりにスレッドを使用してください。
作業を共有作業パイルに入れます。タスク実行がパイルから実際の作業を選択できるように、特定の作業からタスクを分離します。
次のサンプルは、3 つの優先度を実装します。 ユーザー・インターフェイスとトップレベルの実装が続きます。
enum Priority {
P_High,
P_Medium,
P_Low
};
template<typename Func>
void EnqueueWork( Priority p, Func f ) {
WorkItem* item = new ConcreteWorkItem<Func>( p, f );
ReadyPile.add(item);
}
呼び出し元はルーチン EnqueueWork に優先度 p とファンクター f を提供します。 ファンクターはラムダ式によるものかもしれません。 EnqueueWork は f を WorkItem としてパッケージし、グローバル・オブジェクト ReadyPile に追加します。
WorkItem クラスは、不明な型のファンクターを実行するための統一されたインターフェイスを提供します。
// 優先度を付ける作業の抽象基本クラス
class WorkItem {
public:
WorkItem( Priority p ) : priority(p) {}
// 派生クラスは実際の作業を定義
virtual void run() = 0;
const Priority priority;
};
template<typename Func>
class ConcreteWorkItem: public WorkItem { Func f; /* オーバーライド */ void run() { f(); delete this; } public: ConcreteWorkItem( Priority p, const Func& f_ ) : WorkItem(p), f(f_) {} };
ReadyPile クラスはコアパターンを含みます。 作業のコレクションを保持して、コレクションから作業を選択するタスクを送ります。
class ReadyPileType { // 各優先度レベルごとに 1 つのキュー tbb::concurrent_queue<WorkItem*> level[P_Low+1]; public: void add( WorkItem* item ) { level[item->priority].push(item); tbb::task::enqueue(*new(tbb::task::allocate_root()) RunWorkItem); } void runNextWorkItem() { // 項目の優先度順にキューをスキャン WorkItem* item=NULL; for( int i=P_High; i<=P_Low; ++i ) if( level[i].try_pop(item) ) break; assert(item); item->run(); } }; ReadyPileType ReadyPile;
add(item) によってキューに入れられたタスクは、必ずしもその項目を実行するわけではありません。 タスクは runNextWorkItem() を実行し、より優先度の高い項目が見つかることがあります。 各項目につき 1 つのタスクがありますが、マッピングにより、タスクがいつ作成されるかではなく、タスクがいつ実際に実行されるかを指定できます。
RunWorkItem クラスの詳細は次のとおりです。
class RunWorkItem: public tbb::task {
/* オーバーライド */tbb::task* execute(); // 仮想メソッドのプライベート・オーバーライド
};
...
tbb::task* RunWorkItem::execute() {
ReadyPile.runNextWorkItem();
return NULL;
};
RunWorkItem オブジェクトは代替可能です。インテル® TBB のスケジューラーは、これらのオブジェクトを使用して、どの作業項目を行うかではなく、いつ作業項目を行うかを決定します。 呼び出しはすべて task 基本クラス経由でディスパッチされるため、task::execute 仮想メソッドのオーバーライドはプライベートです。
ほかの優先度手法は ReadyPileType の内部を変更することで実装できます。 非常に細粒度の優先度を実装するには、優先度キューを使用することができます。
パターンのスケーラビリティーは ReadyPileType のスケーラビリティーによって制限されるため、 この部分には理想的にスケーラブルなコンカレント・コンテナーを使用するべきです。