ノンプリエンプティブな優先度

ノンプリエンプティブな優先度#

問題

優先順位に基づいて、次に実行するワーク項目を選択します。

コンテキスト

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 を提供します。ファンクターはラムダ式の結果であることがあります。EnqueueWorkfWorkItem としてパッケージ化し、グローバル・オブジェクト 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 のスケーラビリティーによって制限されます。理想的には、スケーラブルな並行コンテナーを使用する必要があります。