C++ で効率的なマルチスレッド タスク スケジューラを作成するにはどうすればよいでしょうか? 質問する

C++ で効率的なマルチスレッド タスク スケジューラを作成するにはどうすればよいでしょうか? 質問する

C++ で非常に効率的なタスク スケジューラ システムを作成したいと思います。

基本的な考え方は次のとおりです。

class Task {
    public:
        virtual void run() = 0;
};

class Scheduler {
    public:
        void add(Task &task, double delayToRun);
};

の背後にはScheduler、タスクを実行する固定サイズのスレッド プールが必要です (タスクごとにスレッドを作成したくありません)。 は、がすぐに実行されるのではなく、数秒後 ( に追加された時点から測定)に実行されるdelayToRunことを意味します。taskdelayToRunScheduler

(delayToRunもちろん、「少なくとも」の値を意味します。システムが負荷をかけられている場合、またはスケジューラに不可能なことを要求した場合、スケジューラは要求を処理できません。ただし、スケジューラは最善を尽くすはずです)

ここで問題があります。delayToRun機能を効率的に実装するにはどうすればよいでしょうか? ミューテックスと条件変数を使用してこの問題を解決しようとしています。

2つの方法があります:

マネージャースレッド付き

allTasksQueueスケジューラには、 、 の2 つのキューが含まれていますtasksReadyToRunQueue。 タスクはallTasksQueueで に追加されます。からScheduler::addにタスクを配置できるように、最小の時間待機するマネージャ スレッドがあります。 ワーカー スレッドは で利用可能なタスクを待機します。allTasksQueuetasksReadyToRunQueuetasksReadyToRunQueue

Scheduler::addの前にタスクを追加する場合allTasksQueue( の値を持つタスクなので、delayToRun現在の最も早く実行されるタスクの前に配置する必要があります)、マネージャー タスクを起動して待機時間を更新する必要があります。

この方法は、2 つのキューが必要であり、タスクを実行するために 2 つの condvar.signal (1 つはallTasksQueue->用tasksReadyToRunQueue、もう 1 つは実際にタスクを実行するためにワーカー スレッドに信号を送るため) が必要であるため、非効率的であると考えられます。

マネージャースレッドなし

スケジューラにはキューが 1 つあります。タスクは 時にこのキューに追加されますScheduler::add。ワーカー スレッドはキューをチェックします。キューが空の場合は、時間制限なしで待機します。空でない場合は、最も早いタスクを待機します。

  1. 作業スレッドが待機している条件変数が1つしかない場合:この方法は非効率的であると考えられる。なぜなら、タスクがキューの先頭に追加された場合(先頭とは、N個の作業スレッドがある場合、タスクインデックス<Nであることを意味する)、全て待機している時間を更新するために、ワーカー スレッドを起動する必要があります。

  2. 各スレッドに個別の条件変数がある場合は、どのスレッドを起動するかを制御できるため、この場合はすべてのスレッドを起動する必要はありません (待機時間が最も長いスレッドのみを起動する必要があるため、この値を管理する必要があります)。現在、これを実装することを考えていますが、正確な詳細を検討するのは複雑です。この方法に関する推奨事項/考え/ドキュメントはありますか?


この問題にはもっと良い解決策がありますか? 標準の C++ 機能を使用しようとしていますが、よりよい解決策が提供される場合は、プラットフォーム依存のツール (私のメイン プラットフォームは Linux) (pthreads など) や Linux 固有のツール (futexes など) も使用したいと思っています。

ベストアンサー1

1 つのプール スレッドが 1 つの条件変数で「次に実行する」タスク (存在する場合) を待機し、残りのプール スレッドが 2 番目の条件変数で無期限に待機する設計を使用することで、個別の「マネージャー」スレッドを持つことと、次に実行するタスクが変更されたときに多数のタスクを起動する必要性を回避できます。

プール スレッドは次のような疑似コードを実行します。

pthread_mutex_lock(&queue_lock);

while (running)
{
    if (head task is ready to run)
    {
        dequeue head task;
        if (task_thread == 1)
            pthread_cond_signal(&task_cv);
        else
            pthread_cond_signal(&queue_cv);

        pthread_mutex_unlock(&queue_lock);
        run dequeued task;
        pthread_mutex_lock(&queue_lock);
    }
    else if (!queue_empty && task_thread == 0)
    {
        task_thread = 1;
        pthread_cond_timedwait(&task_cv, &queue_lock, time head task is ready to run);
        task_thread = 0;
    }
    else
    {
        pthread_cond_wait(&queue_cv, &queue_lock);
    }
}

pthread_mutex_unlock(&queue_lock);

次に実行するタスクを変更する場合は、次を実行します。

if (task_thread == 1)
    pthread_cond_signal(&task_cv);
else
    pthread_cond_signal(&queue_cv);

開催されましたqueue_lock

このスキームでは、すべてのウェイクアップは単一のスレッドで直接行われ、タスクの優先キューは 1 つだけであり、マネージャー スレッドは必要ありません。

おすすめ記事