Wir haben eine Funktion, die ein einzelner Thread aufruft (wir nennen dies den Haupt-Thread). Innerhalb des Funktionskörpers erzeugen wir mehrere Worker-Threads, um CPU-intensive Arbeit zu leisten, warten, bis alle Threads beendet sind, und geben dann das Ergebnis im Haupt-Thread zurück.
Das Ergebnis ist, dass der Aufrufer die Funktion naiv verwenden kann und intern mehrere Kerne verwendet.
Alles gut soweit ..
Das Problem, das wir haben, ist der Umgang mit Ausnahmen. Wir möchten nicht, dass Ausnahmen in den Arbeitsthreads die Anwendung zum Absturz bringen. Wir möchten, dass der Aufrufer der Funktion sie im Hauptthread abfangen kann. Wir müssen Ausnahmen für die Worker-Threads abfangen und sie an den Haupt-Thread weitergeben, damit sie von dort aus weiter abgewickelt werden.
Wie können wir das machen?
Das Beste, was ich mir vorstellen kann, ist:
- Fangen Sie eine ganze Reihe von Ausnahmen in unseren Worker-Threads ab (std :: exception und einige unserer eigenen).
- Notieren Sie den Typ und die Nachricht der Ausnahme.
- Verfügen Sie über eine entsprechende switch-Anweisung im Hauptthread, die Ausnahmen des im Arbeitsthread aufgezeichneten Typs erneut auslöst.
Dies hat den offensichtlichen Nachteil, dass nur eine begrenzte Anzahl von Ausnahmetypen unterstützt wird und Änderungen erforderlich sind, wenn neue Ausnahmetypen hinzugefügt werden.
quelle
Derzeit besteht die einzige tragbare Möglichkeit darin, Catch-Klauseln für alle Arten von Ausnahmen zu schreiben, die Sie möglicherweise zwischen Threads übertragen möchten, die Informationen irgendwo aus dieser Catch-Klausel zu speichern und sie später zum erneuten Auslösen einer Ausnahme zu verwenden. Dies ist der Ansatz von Boost.Exception .
In C ++ 0x können Sie eine Ausnahme mit abfangen
catch(...)
und dann in einerstd::exception_ptr
Verwendungsinstanz speichernstd::current_exception()
. Sie können es dann später aus demselben oder einem anderen Thread mit erneut werfenstd::rethrow_exception()
.Wenn Sie Microsoft Visual Studio 2005 oder höher verwenden, wird die Thread-Bibliothek just :: thread C ++ 0x unterstützt
std::exception_ptr
. (Haftungsausschluss: Dies ist mein Produkt).quelle
Wenn Sie mit C ++ 11, dann
std::future
könnte genau das tun , was Sie suchen: es kann automatisch Falle Ausnahmen , die es an die Spitze des Arbeitsthread machen, und geben sie nicht an dem Punkt , an dem übergeordneten Faden durch dasstd::future::get
ist namens. (Hinter den Kulissen geschieht dies genau wie in der Antwort von @AnthonyWilliams; es wurde bereits für Sie implementiert.)Der Nachteil ist, dass es keinen Standardweg gibt, sich nicht mehr um a zu kümmern
std::future
. Sogar sein Destruktor wird einfach blockiert, bis die Aufgabe erledigt ist. [EDIT 2017: Der Sperr destructor Verhalten ist eine misfeature nur des pseudo-Futures wieder ausstd::async
, die Sie sowieso nie verwenden sollten. Normale Futures blockieren ihren Destruktor nicht. Aber Sie können Aufgaben immer noch nicht "abbrechen", wenn Sie sie verwendenstd::future
: Die Aufgaben, die Versprechen erfüllen, werden weiterhin hinter den Kulissen ausgeführt, auch wenn niemand mehr auf die Antwort hört.] Hier ist ein Spielzeugbeispiel, das möglicherweise klarstellt, was ich tue bedeuten:Ich habe gerade versucht, ein arbeitsähnliches Beispiel mit
std::thread
und zu schreibenstd::exception_ptr
, aber mitstd::exception_ptr
(mit libc ++) läuft etwas schief, sodass ich es noch nicht zum Laufen gebracht habe. :(([EDIT, 2017:
Ich habe keine Ahnung, was ich 2013 falsch gemacht habe, aber ich bin mir sicher, dass es meine Schuld war.]
quelle
f
und dannemplace_back
? Könntest du nicht einfach etwas tunwaitables.push_back(std::async(…));
oder übersehen ich etwas (es kompiliert, die Frage ist, ob es auslaufen könnte, aber ich sehe nicht wie)?wait
? Etwas in der Art, „sobald einer der Jobs fehlgeschlagen ist, spielen die anderen keine Rolle mehr“.async
eine Zukunft eher zurückkommt als etwas anderes). Zu "Also, is there": Nicht instd::future
, aber siehe Sean Parents Vortrag "Better Code: Concurrency" oder meine "Futures from Scratch" für verschiedene Möglichkeiten, dies zu implementieren, wenn es Ihnen nichts ausmacht, die gesamte STL für den Anfang neu zu schreiben. :) Der Schlüsselsuchbegriff lautet "Stornierung".Ihr Problem ist, dass Sie möglicherweise mehrere Ausnahmen von mehreren Threads erhalten, da jede fehlschlagen kann, möglicherweise aus verschiedenen Gründen.
Ich gehe davon aus, dass der Haupt-Thread irgendwie darauf wartet, dass die Threads enden, um die Ergebnisse abzurufen, oder regelmäßig den Fortschritt der anderen Threads überprüft und dass der Zugriff auf gemeinsam genutzte Daten synchronisiert wird.
Einfache Lösung
Die einfache Lösung wäre, alle Ausnahmen in jedem Thread abzufangen und in einer gemeinsam genutzten Variablen (im Haupt-Thread) aufzuzeichnen.
Wenn alle Threads abgeschlossen sind, entscheiden Sie, was mit den Ausnahmen geschehen soll. Dies bedeutet, dass alle anderen Threads ihre Verarbeitung fortgesetzt haben, was möglicherweise nicht das ist, was Sie wollen.
Komplexe Lösung
Die komplexere Lösung besteht darin, dass jeder Ihrer Threads an strategischen Punkten seiner Ausführung überprüft, ob eine Ausnahme von einem anderen Thread ausgelöst wurde.
Wenn ein Thread eine Ausnahme auslöst, wird sie vor dem Beenden des Threads abgefangen, das Ausnahmeobjekt wird in einen Container im Hauptthread kopiert (wie in der einfachen Lösung) und eine gemeinsam genutzte boolesche Variable wird auf true gesetzt.
Und wenn ein anderer Thread diesen Booleschen Wert testet, sieht er, dass die Ausführung abgebrochen werden soll, und bricht auf anmutige Weise ab.
Wenn alle Threads abgebrochen wurden, kann der Haupt-Thread die Ausnahme nach Bedarf behandeln.
quelle
Eine von einem Thread ausgelöste Ausnahme kann im übergeordneten Thread nicht abgefangen werden. Threads haben unterschiedliche Kontexte und Stapel, und im Allgemeinen muss der übergeordnete Thread nicht dort bleiben und warten, bis die untergeordneten Threads fertig sind, damit sie ihre Ausnahmen abfangen können. Es gibt einfach keinen Platz im Code für diesen Fang:
Sie müssen Ausnahmen in jedem Thread abfangen und den Beendigungsstatus von Threads im Hauptthread interpretieren, um eventuell benötigte Ausnahmen erneut auszulösen.
Übrigens ist es in Abwesenheit eines Catch in einem Thread implementierungsspezifisch, ob das Abwickeln des Stapels überhaupt durchgeführt wird, dh die Destruktoren Ihrer automatischen Variablen werden möglicherweise nicht einmal aufgerufen, bevor terminate aufgerufen wird. Einige Compiler tun dies, aber es ist nicht erforderlich.
quelle
Könnten Sie die Ausnahme im Worker-Thread serialisieren, an den Haupt-Thread zurücksenden, deserialisieren und erneut auslösen? Ich gehe davon aus, dass die Ausnahmen alle von derselben Klasse stammen müssen (oder zumindest von einer kleinen Menge von Klassen mit der switch-Anweisung). Ich bin mir auch nicht sicher, ob sie serialisierbar sind, ich denke nur laut nach.
quelle
Es gibt in der Tat keine gute und generische Möglichkeit, Ausnahmen von einem Thread zum nächsten zu übertragen.
Wenn alle Ihre Ausnahmen wie gewünscht von std :: exception abgeleitet sind, können Sie einen allgemeinen Ausnahmefang der obersten Ebene haben, der die Ausnahme irgendwie an den Hauptthread sendet, wo sie erneut ausgelöst wird. Das Problem ist, dass Sie den Wurfpunkt der Ausnahme verlieren. Sie können wahrscheinlich compilerabhängigen Code schreiben, um diese Informationen abzurufen und zu übertragen.
Wenn nicht alle Ihre Ausnahmen std :: exception erben, sind Sie in Schwierigkeiten und müssen viel Catch auf oberster Ebene in Ihren Thread schreiben ... aber die Lösung bleibt bestehen.
quelle
Sie müssen einen generischen Fang für alle Ausnahmen im Worker durchführen (einschließlich nicht standardmäßiger Ausnahmen wie Zugriffsverletzungen) und eine Nachricht vom Worker-Thread (ich nehme an, Sie haben eine Art von Messaging eingerichtet?) An das Controlling senden Thread, der einen Live-Zeiger auf die Ausnahme enthält, und erneut werfen, indem eine Kopie der Ausnahme erstellt wird. Dann kann der Arbeiter das ursprüngliche Objekt freigeben und beenden.
quelle
Siehe http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html . Es ist auch möglich, eine Wrapper-Funktion für jede Funktion zu schreiben, die Sie zum Verbinden eines untergeordneten Threads aufrufen. Dabei wird automatisch (mit boost :: rethrow_exception) jede von einem untergeordneten Thread ausgegebene Ausnahme erneut ausgelöst.
quelle