c ++ Threads innerhalb der Schleife drucken falsche Werte

19

Ich versuche, Multithreading in c ++ zu verstehen, aber ich stecke in diesem Problem fest: Wenn ich Threads in einer for-Schleife starte, drucken sie falsche Werte. Dies ist der Code:

#include <iostream>
#include <list>
#include <thread>

void print_id(int id){
    printf("Hello from thread %d\n", id);
}

int main() {
    int n=5;
    std::list<std::thread> threads={};
    for(int i=0; i<n; i++ ){
        threads.emplace_back(std::thread([&](){ print_id(i); }));
    }
    for(auto& t: threads){
        t.join();
    }
    return 0;
}

Ich hatte erwartet, dass die Werte 0,1,2,3,4 gedruckt werden, aber ich habe oft zweimal den gleichen Wert erhalten. Dies ist die Ausgabe:

Hello from thread 2
Hello from thread 3
Hello from thread 3
Hello from thread 4
Hello from thread 5

Was fehlt mir?

Ermando
quelle
7
Übergeben Sie iden Wert an Lambda [i].
rafix07
1
Es ist erwähnenswert, dass Ihre Verwendung von emplace_backseltsam ist: emplace_backNimmt eine Liste von Argumenten und gibt diese an einen Konstruktor für weiter std::thread. Sie haben eine (rvalue) -Instanz von übergeben std::thread, erstellen daher einen Thread und verschieben diesen Thread in den Vektor. Diese Operation wird besser durch die allgemeinere Methode ausgedrückt push_back. Es wäre sinnvoller, entweder zu schreiben threads.emplace_back([i](){ print_id(i); });(Konstrukt an Ort und Stelle) oder threads.push_back(std::thread([i](){ print_id(i); }));(Konstrukt + Verschieben), die etwas idiomatischer sind.
Milo Brandt

Antworten:

17

Die [&]Syntax bewirkt i, dass sie als Referenz erfasst wird . Daher wird es ziemlich oft iweiter fortgeschritten sein, wenn der Thread ausgeführt wird, als Sie vielleicht erwarten. Im Ernst, das Verhalten Ihres Codes ist undefiniert, wenn ider Gültigkeitsbereich überschritten wird, bevor ein Thread ausgeführt wird.

Erfassung inach Wert - dh std::thread([i](){ print_id(i); })ist die Lösung.

Bathseba
quelle
2
Oder weniger gebraucht und nicht oft ratsamstd::thread([=](){ print_id(i); })
Wander3r
3
Das Verhalten ist bereits undefiniert, da dies ein Datenrennen auf dem (nicht atomaren) ist, ibei dem der Haupt-Thread geschrieben und die anderen Threads gelesen werden.
Walnuss
6

Zwei Probleme:

  1. Sie haben keine Kontrolle darüber, wann der Thread ausgeführt wird. Dies bedeutet, dass der Wert der Variablen iim Lambda möglicherweise nicht Ihren Erwartungen entspricht.

  2. Die Variable iist lokal für die Schleife und nur für die Schleife. Wenn die Schleife beendet wird, bevor ein oder mehrere Threads ausgeführt werden, haben diese Threads einen ungültigen Verweis auf eine Variable, deren Lebensdauer abgelaufen ist.

Sie können diese beiden Probleme sehr einfach lösen, indem Sie die Variable i nach Wert anstatt nach Referenz erfassen . Das bedeutet, dass jeder Thread eine Kopie des Werts hat und diese Kopie für jeden Thread eindeutig erstellt wird.

Ein Programmierer
quelle
5

Eine andere Sache:
Warten Sie nicht, bis Sie immer eine geordnete Sequenz haben: 0, 1, 2, 3, ... denn der Multithreading-Ausführungsmodus hat eine Besonderheit: Indeterminismus .

Indeterminismus bedeutet, dass die Ausführung desselben Programms unter denselben Bedingungen zu einem anderen Ergebnis führt.

Dies liegt an der Tatsache, dass das Betriebssystem Threads von Ausführung zu Ausführung unterschiedlich plant, abhängig von mehreren Parametern: CPU-Auslastung, Priorität anderer Prozesse, mögliche Systemunterbrechungen, ...

Ihr Beispiel enthält nur 5 Threads. Versuchen Sie also einfach, die Anzahl der Threads zu erhöhen. Wenn Sie beispielsweise die Verarbeitungsfunktion in den Ruhezustand versetzen, werden Sie feststellen, dass das Ergebnis von Ausführung zu Ausführung unterschiedlich sein kann.

Landpirscher
quelle