2017-11-06 16 views
0

私は、ジョブが待ち行列にプッシュされるのを待っているスレッドプールをスレッドプールに持っています。C++:スレッドプールとコンテキスト切り替えのスローダウン

私は、1000のジョブをプールのキューに順番に追加するメインアプリケーションスレッドにループを持っています(ジョブを追加し、ジョブが終了するのを待ってから別のジョブx1000を追加します)。実際の並列処理が起こっていないので...ここにいくつかの擬似コードです:

////threadpool: 
class ThreadPool 
{ 
    .... 

    std::condition_variable job_cv; 
    std::condition_variable finished_cv; 
    std::mutex job_mutex; 
    std::queue<std::function <void(void)>> job_queue; 

    void addJob(std::function <void(void)> jobfn) 
    { 
     std::unique_lock <std::mutex> lock(job_mutex); 
     job_queue.emplace(std::move(jobfn)); 
     job_cv.notify_one(); 
    } 

    void waitForJobToFinish() 
    { 
     std::unique_lock<std::mutex> lock(job_mutex); 
     finished_cv.wait(lock, [this]() {return job_queue.empty(); }); 
    } 

    .... 

    void threadFunction() //called by each thread when it's first started 
    { 
     std::function <void(void)> job; 
     while (true) 
     { 
      std::unique_lock <std::mutex> latch(job_mutex); 
      job_cv.wait(latch, [this](){return !job_queue.empty();}); 

      { 
       job = std::move(job_queue.front()); 
       job_queue.pop(); 

       latch.unlock(); 

       job(); 

       latch.lock(); 
       finished_cv.notify_one(); 
      }  
     } 
    } 
} 

...

////main application: 

void jobfn() 
{ 
    //do some lightweight calculation 
} 

void main() 
{ 
    //test 1000 calls to the lightweight jobfn from the thread pool 
    for (int q = 0; q < 1000; q++) 
    {   
     threadPool->addJob(&jobfn); 
     threadPool->waitForJobToFinish(); 
    } 
} 
だから基本的には、ジョブがキューとメインループに追加されている何が起こっているかを待つために開始されます

待機スレッドがそれをピックアップし、スレッドが終了すると、メインループが続行可能であり、キューに他のジョブを追加できるなどの通知をアプリケーションに送信します。そのようにして1000ジョブは順次処理されます。

ジョブ自体は非常に小さく、数ミリ秒で完了することができます。

しかし、私は奇妙な何か....

それが完了するのループにかかる時間は、本質的に、nは、スレッドプール内のスレッド数であるO(n)が気づきました。したがって、すべてのシナリオでジョブが1つずつ処理されても、10スレッドプールは1スレッドプールよりも1000ジョブのジョブを完了するのに10倍の時間がかかります。

私は理由を理解しようとしていますが、私の唯一の推測は、これまでのコンテキスト切り替えがボトルネックであることです...多分、1つのスレッドがジョブを取得しているときにコンテキスト切り替えのオーバーヘッドが必要になります。しかし、10スレッドが一度に1つのジョブを処理するために連続して回っているときには、余分な処理が必要ですか?しかし、それは私には意味がありません...スレッドB、C、Dのように、ジョブのスレッドAをアンロックするのに必要な操作と同じではないでしょうか?別のスレッドがそれを与えられるまでスレッドがコンテキストを失わないOSレベルのキャッシュがありますか?だから、同じスレッドを繰り返し呼び出すのは、スレッドA、B、Cを連続して呼び出すよりも速いのですか?

しかし、これは完全な推測です...他の人がなぜ私がこれらの結果を得ているかについていくつかの洞察を払う可能性があります...直感的に私はただ1つのスレッドが一度に実行されている限り、任意の数のスレッドを持つスレッドプールを持つことができ、[x]ジョブの合計タスク完了時間は同じになります(各ジョブが同一で、ジョブの総数が同じである限り)...なぜですかそれは間違っている?

+0

これは関連していない可能性がありますが、コアの数はいくつですか? – merlin2011

+0

@ merlin2011 16(2 CPU x 8コア) – Tyson

+0

また、これをどのようにベンチマークしていますか?つまり、開始時と終了時の時間を測定しているか、各タスクのデータを収集しているので、超低速の10秒ごとに1であるかどうかを確認できますか? – merlin2011

答えて

0

あなたの「推測」は正しいです。これは単なるリソース競合の問題です。

10スレッドはアイドル状態ではなく、待機中です。これは、OSがアプリケーションの現在アクティブなスレッドを反復処理しなければならないことを意味します。つまり、コンテキスト切り替えが発生する可能性があります。信号が通知された、ロックを取得することができれば、それは可能性のためのタイムスライスにすることができないので

アクティブスレッドが押し戻され、「待機」スレッドは、内のコードをチェックし、前部に引き込まそのスレッドは、ロックを取得できるかどうかを確認しようとしている残りのスレッドを繰り返し処理します。これは、「アクティブ」スレッドがまだ完了するためにタイムスライスに割り当てられていないためです。

OSレベルで追加のスレッドを繰り返す必要がないため、単一スレッドプールにはこの問題はありません。単一スレッドのプールは、jobを1000回呼び出すよりもまだ遅いです。

希望に応じることができます。

関連する問題