2017-11-06 1 views
0

Ich habe einen Thread-Pool mit Leerlauf-Threads, die darauf warten, dass Aufträge an eine Warteschlange in einer Windows-Anwendung verschoben werden.C++: Thread-Pools und Kontextwechsel Verlangsamungen

Ich habe eine Schleife in meinem Hauptanwendungs-Thread, die der Warteschlange des Pools nacheinander 1000 Jobs hinzufügt (fügt einen Job hinzu, wartet dann auf den Job und fügt einen weiteren Job hinzu, x1000). Also keine tatsächliche parallele Verarbeitung geschieht ... hier einige Pseudo-Code:

////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(); 
    } 
} 

Also im Grunde, was ein Job in die Warteschlange hinzugefügt wird, geschieht und die Hauptschleife zu warten beginnt Ein wartender Thread nimmt ihn dann auf, und wenn der Thread fertig ist, benachrichtigt er die Anwendung, dass die Hauptschleife fortgesetzt werden kann und ein anderer Job zur Warteschlange usw. hinzugefügt werden kann. Auf diese Weise werden 1000 Jobs sequenziell verarbeitet.

Es ist erwähnenswert, dass die Jobs selbst winzig sind und in wenigen Millisekunden abgeschlossen werden können.

Allerdings habe ich etwas Seltsames bemerkt ....

Die Zeit, die für die Schleife in Anspruch nimmt im Wesentlichen O (n), wobei n die Anzahl der Threads im Thread-Pool ist. Auch wenn Jobs in allen Szenarios nacheinander verarbeitet werden, dauert ein 10-Thread-Pool 10-mal länger, um die vollständige Aufgabe mit 1000 Jobs abzuschließen als ein 1-Thread-Pool.

Ich versuche herauszufinden, warum, und meine einzige Vermutung ist bisher, dass Kontextwechsel ist der Engpass ... vielleicht weniger (oder null?) Kontextwechsel Overhead ist erforderlich, wenn nur 1 Thread Aufträge ergreift .. Aber wenn 10 Threads ständig an der Reihe sind, um einen einzelnen Job gleichzeitig zu verarbeiten, ist eine zusätzliche Verarbeitung erforderlich? Aber das macht keinen Sinn für mich ... wäre es nicht die gleiche Operation, die erforderlich ist, um Thread A für einen Job zu entsperren, wie es Thread B, C, D ... wäre? Gibt es ein Caching auf Betriebssystemebene, bei dem ein Thread den Kontext nicht verliert, bis er einen anderen Thread erhält? Es ist also schneller, den gleichen Thread immer wieder aufzurufen, als die Threads A, B, C sequenziell aufzurufen.

Aber das ist eine komplette Vermutung an diesem Punkt ... vielleicht könnte jemand anderes einen Einblick geben, warum ich diese Ergebnisse bekomme ... Intuitiv nahm ich an, dass, solange nur 1 Thread zu einer Zeit ausgeführt wird, ich könnte einen Thread-Pool mit einer beliebig großen Anzahl von Threads haben und die Gesamtbearbeitungszeit für [x] Jobs wäre gleich (solange jeder Job identisch ist und die Gesamtzahl der Jobs gleich ist) ... warum ist das ist falsch?

+0

Dies ist möglicherweise nicht verwandt, aber wie viele Kerne haben Sie? – merlin2011

+0

@ merlin2011 16 (2 CPU x 8 Kerne). – Tyson

+0

Wie bewerten Sie das auch? Das heißt, messen Sie die Zeit am Anfang und am Ende, oder sammeln Sie Daten für jede Aufgabe, so dass Sie sagen können, ob 1 von 10 sehr langsam ist? – merlin2011

Antwort

0

Ihre "Rate" ist richtig; es ist einfach ein Ressourcenkonfliktproblem.

Ihre 10 Threads sind nicht im Leerlauf, sie warten. Dies bedeutet, dass das Betriebssystem über die aktuell aktiven Threads für Ihre Anwendung iterieren muss, was bedeutet, dass wahrscheinlich ein Kontextwechsel stattfindet.

Der aktive Thread wird zurückgeschoben, ein "wartender" Thread wird nach vorne gezogen, in dem der Code prüft, ob das Signal gemeldet wurde und die Sperre akquiriert werden kann, da es wahrscheinlich nicht in der Zeitscheibe für Dieser Thread, iteriert weiter über die verbleibenden Threads, wobei jeder versucht zu sehen, ob die Sperre akquiriert werden kann, was nicht möglich ist, weil Deinem "aktiven" Thread noch kein Zeitabschnitt zugewiesen wurde, um ihn abzuschließen.

Ein Single-Thread-Pool hat dieses Problem nicht, da keine zusätzlichen Threads auf Betriebssystemebene iteriert werden müssen; gewährt, ist ein Single-Thread-Pool immer noch langsamer als nur job 1000 mal aufrufen.

Hoffe, dass kann helfen.

Verwandte Themen