Was passiert mit einem getrennten Thread, wenn main () beendet wird?

152

Angenommen, ich starte a std::threadund dann detach()es, sodass der Thread weiterhin ausgeführt wird, obwohl der Thread, der std::threadihn einmal dargestellt hat, außerhalb des Gültigkeitsbereichs liegt.

Angenommen, das Programm verfügt nicht über ein zuverlässiges Protokoll zum Verbinden des getrennten Threads 1 , sodass der getrennte Thread beim main()Beenden weiterhin ausgeführt wird.

Ich kann im Standard (genauer gesagt im Entwurf von N3797 C ++ 14) nichts finden, was beschreibt, was passieren soll. Weder 1.10 noch 30.3 enthalten relevante Formulierungen.

1 Eine andere, wahrscheinlich äquivalente Frage lautet: "Kann ein losgelöster Thread jemals wieder verbunden werden?" Entscheiden Sie sich, den Thread kurz nach der Signalisierung für eine Stunde in den Ruhezustand zu versetzen, ohne dass das empfangende Ende zuverlässig erkennen kann, dass der Thread tatsächlich beendet wurde.

Wenn das Auslaufen main()mit getrennten Threads ein undefiniertes Verhalten ist, ist jede Verwendung von std::thread::detach()undefiniertem Verhalten, es sei denn, der Hauptthread wird nie beendet 2 .

Das Auslaufen main()mit abgelösten Threads muss daher definierte Auswirkungen haben. Die Frage ist: Wo (im C ++ - Standard nicht POSIX, nicht OS-Dokumente, ...) sind diese Effekte definiert.

2 Ein losgelöster Faden kann nicht verbunden werden (im Sinne von std::thread::join()). Sie können auf Ergebnisse von getrennten Threads warten (z. B. über eine Zukunft von std::packaged_taskoder durch ein Zählsemaphor oder ein Flag und eine Bedingungsvariable), dies garantiert jedoch nicht, dass die Ausführung des Threads abgeschlossen ist . Wenn Sie den Signalisierungsteil nicht in den Destruktor des ersten automatischen Objekts des Threads einfügen , gibt es im Allgemeinen Code (Destruktoren), der nach dem Signalisierungscode ausgeführt wird. Wenn das Betriebssystem plant, dass der Hauptthread das Ergebnis verbraucht und beendet wird, bevor der getrennte Thread die Ausführung der Destruktoren beendet, was wird dann definiert?

Marc Mutz - mmutz
quelle
5
Ich kann nur in [basic.start.term] / 4 einen sehr vagen, nicht obligatorischen Hinweis finden: "Das Beenden jedes Threads vor einem Aufruf std::exitoder dem Verlassen von mainist ausreichend, aber nicht notwendig, um diese Anforderungen zu erfüllen." (Der gesamte Absatz kann relevant sein.) Siehe auch [support.start.term] / 8 ( std::exitwird aufgerufen, wenn mainzurückgegeben wird)
dyp

Antworten:

45

Die Antwort auf die ursprüngliche Frage "Was passiert mit einem getrennten Thread beim main()Beenden?" Lautet:

Es läuft weiter (weil der Standard nicht sagt, dass es gestoppt ist), und das ist gut definiert, solange es weder (automatische | thread_local) Variablen anderer Threads noch statische Objekte berührt.

Dies scheint erlaubt zu sein, Thread-Manager als statische Objekte zuzulassen (Hinweis in [basic.start.term] / 4 sagt dies dank @dyp für den Zeiger).

Probleme treten auf, wenn die Zerstörung statischer Objekte abgeschlossen ist, da dann die Ausführung in ein Regime eintritt, in dem nur Code ausgeführt werden darf, der in Signalhandlern zulässig ist ( [basic.start.term] / 1, 1. Satz ). Von der C ++ - Standardbibliothek ist dies nur die <atomic>Bibliothek ( [support.runtime] / 9, 2. Satz ). Insbesondere schließt condition_variable dies im Allgemeinen aus (es ist implementierungsdefiniert, ob dies für die Verwendung in einem Signalhandler gespeichert ist, da es nicht Teil von ist <atomic>).

Wenn Sie Ihren Stapel zu diesem Zeitpunkt nicht abgewickelt haben, ist es schwer zu erkennen, wie Sie undefiniertes Verhalten vermeiden können.

Die Antwort auf die zweite Frage "Können getrennte Threads jemals wieder verbunden werden?" Lautet:

Ja, mit der *_at_thread_exitFamilie von Funktionen ( notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ...).

Wie in Fußnote [2] der Frage erwähnt, reicht das Signalisieren einer Bedingungsvariablen oder eines Semaphors oder eines Atomzählers nicht aus, um sich einem getrennten Thread anzuschließen (in dem Sinne, dass das Ende seiner Ausführung vor dem Empfang von stattgefunden hat) die Signalisierung durch einen wartenden Thread), da im Allgemeinen mehr Code ausgeführt wird, z. B. notify_all()nach einer Bedingungsvariablen, insbesondere nach den Destruktoren von automatischen und threadlokalen Objekten.

Das Ausführen der Signalisierung als letztes, was der Thread tut ( nachdem Destruktoren von automatischen und threadlokalen Objekten aufgetreten sind ), ist das, wofür die _at_thread_exitFunktionsfamilie entwickelt wurde.

Um undefiniertes Verhalten zu vermeiden, wenn keine Implementierungsgarantien über den Standardanforderungen liegen, müssen Sie einem getrennten Thread (manuell) eine _at_thread_exitFunktion zur Signalisierung hinzufügen oder den getrennten Thread nur Code ausführen lassen , für den dies sicher wäre auch ein Signalhandler.

Marc Mutz - mmutz
quelle
17
Bist du dir da sicher? Überall, wo ich getestet habe (GCC 5, Clang 3.5, MSVC 14), werden alle abgelösten Threads getötet, wenn der Haupt-Thread beendet wird.
Rustyx
3
Ich glaube, dass es nicht darum geht, was eine bestimmte Implementierung tut, sondern darum, zu vermeiden, was der Standard als undefiniertes Verhalten definiert.
Jon Spencer
7
Diese Antwort scheint zu implizieren, dass der Prozess nach der Zerstörung statischer Variablen in einen Ruhezustand übergeht und darauf wartet, dass alle verbleibenden Threads beendet werden. Dies ist nicht der Fall. Nach exitAbschluss der Zerstörung statischer Objekte, Ausführen von atexitHandlern, Löschen von Streams usw. wird die Kontrolle an die Hostumgebung zurückgegeben, dh der Prozess wird beendet. Wenn ein losgelöster Thread noch läuft (und irgendwie undefiniertes Verhalten vermieden hat, indem nichts außerhalb seines eigenen Threads berührt wurde), verschwindet er einfach in einer Rauchwolke, wenn der Prozess beendet wird.
Jonathan Wakely
3
Wenn Sie Nicht-ISO-C ++ - APIs verwenden können , wird der Prozess, wenn mainAufrufe pthread_exitanstelle von Rückgabe oder Aufruf ausgeführt exitwerden, darauf warten, dass getrennte Threads beendet werden, und dann exitnach Abschluss des letzten Threads aufgerufen .
Jonathan Wakely
3
"Es läuft weiter (weil der Standard nicht sagt, dass es gestoppt ist)" -> Kann mir jemand sagen, wie ein Thread die Ausführung ohne seinen Containerprozess fortsetzen kann?
Gupta
42

Fäden abnehmen

Laut std::thread::detach:

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.

Von pthread_detach:

Die Funktion pthread_detach () muss der Implementierung anzeigen, dass der Speicher für den Thread zurückgefordert werden kann, wenn dieser Thread beendet wird. Wenn der Thread nicht beendet wurde, wird er durch pthread_detach () nicht beendet. Die Auswirkung mehrerer Aufrufe von pthread_detach () auf denselben Zielthread ist nicht angegeben.

Das Trennen von Threads dient hauptsächlich zum Einsparen von Ressourcen, falls die Anwendung nicht auf das Beenden eines Threads warten muss (z. B. Daemons, die bis zur Beendigung des Prozesses ausgeführt werden müssen):

  1. So lösen Sie den anwendungsseitigen Griff: Man kann ein std::threadObjekt außerhalb des Bereichs verlassen, ohne sich zu verbinden, was normalerweise zu einem Aufruf zur std::terminate()Zerstörung führt.
  2. Damit das Betriebssystem die threadspezifischen Ressourcen ( TCB ) automatisch bereinigen kann, sobald der Thread beendet wird, da wir ausdrücklich angegeben haben, dass wir nicht daran interessiert sind, dem Thread später beizutreten, kann man einem bereits getrennten Thread nicht beitreten.

Fäden töten

Das Verhalten beim Beenden des Prozesses ist das gleiche wie beim Hauptthread, der zumindest einige Signale abfangen könnte. Ob andere Threads Signale verarbeiten können oder nicht, ist nicht so wichtig, da andere Threads innerhalb des Signalhandler-Aufrufs des Haupt-Threads verbunden oder beendet werden können. (Verwandte Frage )

Wie bereits erwähnt, stirbt jeder Thread, unabhängig davon, ob er getrennt ist oder nicht, auf den meisten Betriebssystemen . Der Prozess selbst kann durch Auslösen eines Signals, Aufrufen exit()oder Zurückkehren von der Hauptfunktion beendet werden. C ++ 11 kann und will jedoch nicht versuchen, das genaue Verhalten des zugrunde liegenden Betriebssystems zu definieren, während die Entwickler einer Java-VM solche Unterschiede sicherlich bis zu einem gewissen Grad abstrahieren können. AFAIK-, exotische Prozess- und Threading-Modelle finden sich normalerweise auf alten Plattformen (auf die C ++ 11 wahrscheinlich nicht portiert wird) und verschiedenen eingebetteten Systemen, die eine spezielle und / oder eingeschränkte Implementierung der Sprachbibliothek und auch eingeschränkte Sprachunterstützung aufweisen könnten.

Thread-Unterstützung

Wenn Threads nicht unterstützt werden, std::thread::get_id()sollte eine ungültige ID (standardmäßig erstellt std::thread::id) zurückgegeben werden, da es einen einfachen Prozess gibt, für dessen Ausführung kein Thread-Objekt erforderlich ist und der Konstruktor von a eine std::threadauslösen sollte std::system_error. So verstehe ich C ++ 11 in Verbindung mit den heutigen Betriebssystemen. Wenn es ein Betriebssystem mit Threading-Unterstützung gibt, das in seinen Prozessen keinen Haupt-Thread erzeugt, lassen Sie es mich wissen.

Threads steuern

Wenn Sie die Kontrolle über einen Thread behalten müssen, um ihn ordnungsgemäß herunterzufahren, können Sie dies mithilfe von Synchronisierungsprimitiven und / oder einer Art von Flags tun. In diesem Fall bevorzuge ich jedoch das Setzen eines Shutdown-Flags gefolgt von einem Join, da es keinen Sinn macht, die Komplexität durch Trennen von Threads zu erhöhen, da die Ressourcen ohnehin gleichzeitig freigegeben würden, wenn die wenigen Bytes des std::threadObjekts vorhanden wären Eine höhere Komplexität und möglicherweise mehr Synchronisationsprimitive sollten akzeptabel sein.

Sam
quelle
3
Da jeder Thread seinen eigenen Stack hat (der unter Linux im Megabyte-Bereich liegt), würde ich den Thread trennen (damit sein Stack beim Beenden freigegeben wird) und einige Synchronisierungsprimitive verwenden, wenn der Hauptthread beendet werden muss (und für ein ordnungsgemäßes Herunterfahren müssen die noch laufenden Threads verbunden werden, anstatt sie beim Zurück- / Beenden zu beenden).
Norbert Bérci
8
Ich sehe wirklich nicht, wie dies die Frage beantwortet
MikeMB
18

Betrachten Sie den folgenden Code:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

Wenn Sie es auf einem Linux-System ausführen, wird die Nachricht von thread_fn nie gedruckt. Das Betriebssystem wird tatsächlich bereinigt, thread_fn()sobald es beendet wird main(). Durch Ersetzen t1.detach()durch wird t1.join()die Nachricht immer wie erwartet gedruckt.

kgvinod
quelle
Dieses Verhalten tritt genau unter Windows auf. Es scheint also, dass Windows die getrennten Threads beendet, wenn das Programm beendet ist.
Gupta
17

Das Schicksal des Threads nach dem Beenden des Programms ist undefiniertes Verhalten. Ein modernes Betriebssystem bereinigt jedoch alle Threads, die durch den Prozess beim Schließen erstellt wurden.

Beim Abnehmen von bleiben std::threaddiese drei Bedingungen weiterhin gültig:

  1. *this besitzt keinen Thread mehr
  2. joinable() wird immer gleich sein false
  3. get_id() wird gleich sein std::thread::id()
Caesar
quelle
1
Warum undefiniert? Weil der Standard nichts definiert? Wäre das nach meiner Fußnote kein Aufruf zu detach()undefiniertem Verhalten? Kaum zu glauben ...
Marc Mutz - mmutz
2
@ MarcMutz-mmutz Es ist undefiniert in dem Sinne, dass das Schicksal des Threads undefiniert ist, wenn der Prozess beendet wird.
Caesar
2
@Caesar und wie stelle ich sicher, dass das Programm nicht beendet wird, bevor der Thread beendet ist?
MichalH
0

Damit andere Threads die Ausführung fortsetzen können, sollte der Hauptthread durch Aufrufen von pthread_exit () und nicht von exit (3) beendet werden. Es ist in Ordnung, pthread_exit in main zu verwenden. Wenn pthread_exit verwendet wird, wird der Hauptthread nicht mehr ausgeführt und bleibt im Zombie-Status (nicht mehr aktiv), bis alle anderen Threads beendet werden. Wenn Sie pthread_exit im Hauptthread verwenden, den Rückgabestatus anderer Threads nicht abrufen und keine Bereinigung für andere Threads durchführen können (dies kann mit pthread_join (3) erfolgen). Außerdem ist es besser, Threads (pthread_detach (3)) zu trennen, damit Thread-Ressourcen beim Beenden des Threads automatisch freigegeben werden. Die freigegebenen Ressourcen werden erst freigegeben, wenn alle Threads beendet sind.

yshi
quelle
@kgvinod, warum nicht "pthread_exit (0)" hinzufügen? nach dem "ti.detach ()";
Yshi