ノンプリエンプティブな優先度#
問題
優先順位に基づいて、次に実行するワーク項目を選択します。
コンテキスト
oneAPI スレッディング・ビルディング・ブロック (oneTBB) のスケジューラーは、スケーラビリティーに関する考慮事項に基づいたルールによってタスクを選択します。ルールはタスクが生成された順序、またはキューに投入された順序に基づいており、タスクの内容は考慮されません。しかし、時には、何らかの優先順位関係を基にワークを選択するのが最善であることもあります。
強制
複数のワーク項目がある場合、デフォルトの oneTBB ルールではない、次に実行すべき項目を決定するルールがあります。
プリエンプティブな順位は必要ありません。優先度の高い項目が現れても、実行中の優先度の低い項目を直ちに停止する必要はありません。プリエンプティブな優先順位が必要である場合に、ノンプリエンプティブなタスクを処理するのは不適切です。代わりスレッドを使用します。
解決方法
ワークを共有ワークパイルに格納します。タスクを特定のワークから分離して、タスクの実行時にパイルの中から実際に選択されるワークを選択するようにします。
例
次の例では、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 クラスにはコアパターンが含まれています。ワークのコレクションを維持し、oneapi::tbb::task_group::run インターフェイスを介してタスクを起動し、コレクションからワークを選択します。
class ReadyPileType {
// 優先度ごとに 1 つのキュー
oneapi::tbb::concurrent_queue<WorkItem*> level[P_Low+1];
oneapi::tbb::task_group tg;
public:
void add( WorkItem* item ) {
level[item->priority].push(item);
tg.run(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) で追加されたタスクは、必ずしもその項目 (item) を実行するわけではありません。タスク自体は runNextWorkItem() を実行して、これによりより優先度の高い項目が見つかる可能性があります。各項目には 1 つのタスクがありますが、マッピングはタスクが作成された時点ではなく、実際に実行された時点で解決されます。
RunWorkItem クラスの詳細は次のとおりです。
class RunWorkItem {
void operator()() {
ReadyPile.runNextWorkItem();
};
};RunWorkItem オブジェクトは代替可能です。これにより、oneTBB スケジューラーは、どのワーク項目を実行するかではなく、いつワーク項目を実行するか選択できるようになります。
ReadyPileType 内部を変更することで、他の優先順位スキームを実装できます。優先度キューを使用すると、細粒度の優先度を実装できます。
パターンのスケーラビリティーは、ReadyPileType のスケーラビリティーによって制限されます。理想的には、スケーラブルな並行コンテナーを使用する必要があります。