Wann sollte ich std :: thread :: remove verwenden?


138

Manchmal muss ich std::threadmeine Anwendung beschleunigen. Ich weiß auchjoin() wartet, bis ein Thread abgeschlossen ist. Das ist leicht zu verstehen, aber was ist der Unterschied zwischen Anrufen detach()und Nichtanrufen?

Ich dachte das ohne detach() , die Methode des Threads mit einem Thread unabhängig arbeiten wird.

Nicht ablösen:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

Anrufen mit Abnehmen:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}

1
Mögliches Duplikat von getrennten vs. verbindbaren POSIX-Threads
n. 'Pronomen' m.

Beide stdund boostThreads haben detachund sind joineng an POSIX-Threads angelehnt.
n. 'Pronomen' m.

Antworten:


148

Im Destruktor std::thread, std::terminatewird aufgerufen , wenn:

  • Der Thread wurde nicht verbunden (mit t.join())
  • und wurde auch nicht losgelöst (mit t.detach())

Daher sollten Sie immer entweder joinoder detacheinen Thread verwenden, bevor die Ausführungsflüsse den Destruktor erreichen.


Wenn ein Programm beendet wird (dh main zurückkehrt), wird nicht auf die verbleibenden getrennten Threads gewartet, die im Hintergrund ausgeführt werden. Stattdessen wird ihre Ausführung angehalten und ihre threadlokalen Objekte zerstört.

Entscheidend ist, dass der Stapel dieser Threads nicht abgewickelt wird und daher einige Destruktoren nicht ausgeführt werden. Abhängig von den Aktionen, die diese Destruktoren ausführen sollten, kann dies eine so schlimme Situation sein, als ob das Programm abgestürzt oder getötet worden wäre. Hoffentlich hebt das Betriebssystem die Sperren für Dateien usw. auf, aber Sie könnten den gemeinsam genutzten Speicher, halbgeschriebene Dateien und dergleichen beschädigt haben.


Also, solltest du joinoder verwenden detach?

  • Verwenden join
  • Sofern Sie mehr Flexibilität haben müssen , und sind bereit , einen Synchronisationsmechanismus zu schaffen , für den Thread Abschluss warten auf eigene Faust , in diesem Fall können Sie verwendendetach

Wenn ich pthread_exit (NULL) aufrufen würde; In main () wird exit () nicht von main () aufgerufen, und daher setzt das Programm die Ausführung fort, bis alle getrennten Threads abgeschlossen sind. Dann würde exit () aufgerufen.
Southton

1
@Matthieu, warum können wir uns nicht dem Destruktor von std :: thread anschließen?
John Smith

2
@ Johnsmith: Eine ausgezeichnete Frage! Was passiert, wenn Sie beitreten? Sie warten, bis der Thread abgeschlossen ist. Wenn eine Ausnahme ausgelöst wird, werden Destruktoren ausgeführt ... und plötzlich wird die Weitergabe Ihrer Ausnahme ausgesetzt, bis der Thread beendet wird. Es gibt viele Gründe, dies nicht zu tun, insbesondere wenn es auf die Eingabe des aktuell angehaltenen Threads wartet! Daher entschieden sich die Designer dafür, eine explizite Entscheidung zu treffen, anstatt eine umstrittene Standardeinstellung zu wählen.
Matthieu M.

@Matthieu Ich denke du meinst join () aufrufen, bevor der Destruktor von std :: thread erreicht ist. Sie können (und sollten?) Dem Destruktor einer einschließenden Klasse beitreten ()?
Jose Quinteiro

4
@JoseQuinteiro: Eigentlich, im Gegensatz zu anderen Ressourcen, ist es ratsam , nicht von einem Destruktor aus beizutreten. Das Problem ist , dass sich Verbindungs nicht am Ende ein Gewinde, es lediglich wartet für sie zu beenden, und im Gegensatz zu Ihnen ein Signal an der richtigen Stelle den Faden zu veranlassen , beenden Sie können für eine lange Zeit warten ... blockiert den aktuellen Thread , dessen Stapel wird abgewickelt und verhindert, dass dieser aktuelle Thread jemals beendet wird, wodurch der darauf wartende Thread usw. blockiert wird. Wenn Sie also nicht sicher sind , dass Sie einen bestimmten Thread in angemessener Zeit stoppen können, sollten Sie nicht warten es in einem Destruktor.
Matthieu M.

25

Sie sollten anrufen, detachwenn Sie nicht auf den Abschluss des Threads warten möchten, joinsondern der Thread stattdessen nur so lange ausgeführt wird, bis er fertig ist, und dann beenden, ohne dass der Haupt-Thread speziell darauf wartet.

detachGrundsätzlich werden die Ressourcen freigegeben, die für die Implementierung erforderlich sind join.

Es ist ein schwerwiegender Fehler , wenn ein Thread - Objekt sein Leben endet und weder joinnoch detachgenannt wurde; in diesem Fall terminatewird aufgerufen.


12
Sie sollten erwähnen, dass terminate im Destruktor aufgerufen wird, wenn der Thread weder verbunden noch getrennt wurde .
Nosid

11

Wenn Sie den Thread trennen, bedeutet dies, dass Sie dies nicht müssen join() ihn vor dem Beendenmain() .

Die Thread-Bibliothek wartet tatsächlich auf jeden solchen Thread unter main , aber Sie sollten sich nicht darum kümmern.

detach()Dies ist vor allem dann nützlich, wenn Sie eine Aufgabe haben, die im Hintergrund ausgeführt werden muss, deren Ausführung Ihnen jedoch egal ist. Dies ist normalerweise bei einigen Bibliotheken der Fall. Möglicherweise erstellen sie stillschweigend einen Hintergrund-Worker-Thread und trennen ihn, sodass Sie ihn nicht einmal bemerken.


Dies beantwortet die Frage nicht. Die Antwort lautet im Grunde "Sie lösen sich, wenn Sie sich lösen".
Rubenvb

7

Diese Antwort zielt darauf ab, die Frage im Titel zu beantworten, anstatt den Unterschied zwischen joinund zu erklären detach. Also wann solltestd::thread::detach sollte es verwendet werden?

In ordnungsgemäß gepflegtem C ++ std::thread::detachsollte Code überhaupt nicht verwendet werden. Der Programmierer muss sicherstellen, dass alle erstellten Threads ordnungsgemäß beendet werden, um alle erfassten Ressourcen freizugeben und andere erforderliche Bereinigungsaktionen auszuführen. Dies bedeutet, dass das Aufgeben des Eigentums an Threads durch Aufrufen detachkeine Option ist und daher joinin allen Szenarien verwendet werden sollte.

Einige Anwendungen basieren jedoch auf alten und oft nicht gut gestalteten und unterstützten APIs, die möglicherweise unbegrenzt blockierende Funktionen enthalten. Das Verschieben von Aufrufen dieser Funktionen in einen dedizierten Thread, um das Blockieren anderer Inhalte zu vermeiden, ist eine gängige Praxis. Es gibt keine Möglichkeit, einen solchen Thread ordnungsgemäß zu beenden, sodass die Verwendung von joinnur zum Blockieren des primären Threads führt. Dies ist eine Situation, in der die Verwendung detacheine weniger böse Alternative wäre, um beispielsweise ein threadObjekt mit dynamischer Speicherdauer zuzuweisen und es dann absichtlich zu verlieren.

#include <LegacyApi.hpp>
#include <thread>

auto LegacyApiThreadEntry(void)
{
    auto result{NastyBlockingFunction()};
    // do something...
}

int main()
{
    ::std::thread legacy_api_thread{&LegacyApiThreadEntry};
    // do something...
    legacy_api_thread.detach();
    return 0;
}

1

Laut cppreference.com :

Trennt den Ausführungsthread vom Thread-Objekt, sodass die Ausführung unabhängig fortgesetzt werden kann. Alle zugewiesenen Ressourcen werden freigegeben, sobald der Thread beendet wird.

Nach dem Aufruf *thisbesitzt remove keinen Thread mehr.

Beispielsweise:

  std::thread my_thread([&](){XXXX});
  my_thread.detach();

Beachten Sie die lokale Variable: my_threadWährend die Lebensdauer von abgelaufen my_threadist, wird der Destruktor von std::threadaufgerufen und std::terminate()innerhalb des Destruktors aufgerufen.

Aber wenn Sie verwenden detach(), sollten Sie nicht my_threadmehr verwenden, auch wenn die Lebensdauer von abgelaufen my_threadist, wird dem neuen Thread nichts passieren.


OK, ich nehme zurück, was ich gerade gesagt habe. @ TobySpeight
DinoStray

1
Beachten Sie, dass Sie bei Verwendung &in der Lambda-Erfassung die Variablen des umschließenden Bereichs als Referenz verwenden. Stellen Sie daher besser sicher, dass die Lebensdauer Ihrer Referenz länger ist als Ihre Thread-Lebensdauer.
DavidJ
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.