Thread Klasse, Thread sauber beenden
-
Hallo,
ich wollte eine Threading Klasse schreiben. Ja, ich weiß, dass es Boost usw. gibt, aber ich brauche nur eine simple Threading Klasse, die auch nur unter Windows läuft.
Ist auch alles kein Problem. Ich frage mich nur, wie man es realisiert einen Thread sauber zu beenden.
Ist ja grundsätzlich kein Problem, nur es gibt Situationen, da hat man eine Endlosschleife. Und in dieser gibts wieder Schleifen, Funktionen, usw... Wie stellt man es also möglichst schlau an, den Thread zu beenden, wenn von außen das Beenden angefordert wird?
Ich hatte mir überlegt, dass der Thread einfach eine Funktion abfragt:
while (IsActive()) { // ... }
Nur wenn ich dann in der While wieder eine Schleife hab müsste die das auch abfragen, usw. damit der Thread auch wirklich aus allen Schleifen kommt und nicht irgendwo "hängen" bleibt.
Kann man da irgendwas machen? Gibts da Tricks? Habt ihr Ideen?
-
Willkommen bei der Threading ! Ein recht schönes Thema, da manchmal sehr komplex.
Kann man da irgendwas machen
Problem: Nur die Threadfunktion weis im Endeffekt wann sie sich beenden kann. Bsp.:
while (IsActive()) { int* a = new int[100]; LOCK* Lock; // Lock Synchronisation: http://de.wikipedia.org/wiki/Lock // ... Lock->Acquire(); // print("Hallo..."); // ... // <- Wenn hier der Thread unterbrochen wird, gibt's ein Speicherloch // plus je nach Lage ein Deadlock // delete[] a; Lock->Release(); }
-
Du kommst nicht drum herum bestimmte "Checkpoints" einzubauen die dann prüfen ob der Thread beendet werden sollen.
Der eine "Trick" den es gibt, is in der "Checkpoint" Funktion eine Exception zu werfen wenn der Thread beendet werden soll.
Wenn der ganze Code der in dem Thread ausgeführt wird Exception-safe ist (was er sowieso sein sollte), dann ist das eine gute Möglichkeit den Thread zu beenden, ohne überalls "if (!IsActive()) return" oder Ähnliches einzubauen.Das einzig doofe dabei ist, wenn die Exception gefangen wird, aber nicht weitergeworfen. z.B. weil man irgendwo ein "catch (...)" drinnen hat.
Sinnvollerweise leitet man die Exception-Klasse dann auch nicht von std::exception ab, da sonst nicht nur ein "catch (...)" sondern auch jedes "catch (std::exception)" ein Problem machen würde.
Eine andere Möglichkeit wäre Win32 Structured Exceptions zu verwenden. Das sollte zumindest mit MSVC funktionieren, da MSVC auch für Structured Exceptions korrektes "Stack Unwinding" macht. Und hätte den Vorteil, dass eine Win32 Structured Exception nicht mit "catch (...)" gefangen werden kann.
Dann baust du eine Funktion ala so
void CheckForThreadCancellation() { if (...) // prüfen ob der Thread beendet werden soll throw MyThreadCancellationException(); // bzw. das Win32 Structured Exception Äquivalent }
und rufst diese einfach immer wieder mal auf.
Wenn du irgendwelche Framework-Funktionen hast, die von den Worker-Threads immer wieder man aufgerufen werden, kannst du diese zu "cancellation points" erklären, und dort auch einfach die "CheckForThreadCancellation()" Funktion aufrufen.
Dann können auch Threads beendet werden die nie selbst direkt "CheckForThreadCancellation()" aufrufen, so lange sie eine der "cancellation point" Framework-Funktionen aufrufen.
-
Bitte ein Bit schrieb:
Willkommen bei der Threading ! Ein recht schönes Thema, da manchmal sehr komplex.
Kann man da irgendwas machen
Problem: Nur die Threadfunktion weis im Endeffekt wann sie sich beenden kann. Bsp.:
while (IsActive()) { int* a = new int[100]; LOCK* Lock; // Lock Synchronisation: http://de.wikipedia.org/wiki/Lock // ... Lock->Acquire(); // print("Hallo..."); // ... // <- Wenn hier der Thread unterbrochen wird, gibt's ein Speicherloch // plus je nach Lage ein Deadlock // delete[] a; Lock->Release(); }
Das ist nur dann ein Problem, wenn der Code den man so schreibt nicht Exception-Safe ist.
Wenn man Locks und Resourcen mit RAII verwaltet, gibt es das Problem mit Resource- bzw. Lock-Leaks schonmal nicht.Was dann noch bleibt ist das Problem von unvollständigen Updates von Shared State (also wenn der Code zwischen Lock und Unlock einer Mutex nicht komplett durchläuft, und bereits einen Teil der Änderungen gemacht hat, ein Teil aber noch fehlt).
Das kann man nur verhindern indem man entweder über z.B. Destruktoren von Hilfsklassen "Undo Code" ausführen lässt, oder dafür sorgt dass der entsprechende Codeabschnitt einfach nicht unterbrochen wird.Wenn man im entsprechenden Codeabschnitt einfach keine Funktionen aufruft die überprüfen ob der Thread beendet werden sollte, gibt es sowieso kein Problem. Und wenn doch, dann kann man den Thread-Cancellation Mechanismus immer noch erweitern, indem man z.B. Hilfsklassen bereitstellt die für einen bestimmten Abschnitt das Werfen von Cancel-Exceptions unterdrücken.
-
Hey, vielen Dank für die Antworten. An Exceptions hatte ich auch schon gedacht, aber irgendwas hatte mich davon abgehalten den Gedanken weiter zu fassen. Ich weiß leider nicht mehr was es war, aber kann nicht so wichtig gewesen sein.
Eine simple Funktion aufzurufen, die einfach nur eine Exception wirft, wenn der Thread beendet werden soll, find ich eigentlich einen guten Kompromiss. Damit kann ich gleichzeitig gut kontrollieren, wann der Thread sich überhaupt beenden darf ohne in einen inkonsistenzen Zustand zu rutschen.
Mein Mutex hab ich ohnehin mit RAII realisiert. Damit sollte ja sogar ein Deadlock ausgeschlossen sein, oder? Schließlich wird der Stack ja aufgeräumt und das Objekt gibt durch den Destruktor den Mutex wieder frei.
Wenn das wirklich so ist, dann wäre die Lösung definitiv echt optimal. Eigentlich hab ich so auch fast alles was ich brauche. Das einzige was ich nicht so gut realisieren kann ist ein Sleep.
Die Frage ist daher, soll ich es so umsetzen, dass ich mir ein eigenes Sleep so nachbaue, dass es einfach die Millisekunden weiter runterbricht und in kleinere Sleep-Aufrufe aufteilt, damit es zwischendrin mal gucken kann, ob der Thread noch laufen darf.
Oder soll ich das mit select() lösen, falls das überhaupt einen Unterschied macht. Oder gibts eine noch bessere Lösung?
Das wäre das einzige, was mir noch fehlen würde. Ansonsten echt eine top Lösung, danke dafür