Seltsame Symptome, die exit(), _exit(), ExitProcess() und andere in einem Multithread-Programm enthalten - insbesondere wenn die Symptome zwischen Hosts variieren - haben den Geruch einer Variablen, die von verschiedenen Threads geändert oder aufgerufen wird, ohne Synchronisation . Wenn Sie sich den anderen Thread ansehen, mit dem Sie verbunden sind, scheint es, dass Sie eine flüchtige Variable verwenden, um zwischen Threads zu kommunizieren, aber keine Form der Synchronisation (z. B. Code, der auf den Wert dieser Variablen zugreift und Code, der dies ändert) Wert muss über einen kritischen Abschnitt, Mutex oder ein vergleichbares Konstrukt kooperieren).
Das bisschen indirekte Beweise macht den Geruch noch stärker.Das grundlegende Problem, das ich vermute, ist, dass die Deklaration einer Variablen als flüchtig weder notwendig noch ausreichend ist, um sicherzustellen, dass Variablen immer Werte haben, die für Ihr Programm sinnvoll sind. Insbesondere reicht es nicht aus, zu verhindern, dass ein Thread, der eine Variable verändert, vorzeitig beendet wird, wenn die Modifikation nur teilweise abgeschlossen ist, und dass ein anderer Thread versucht, auf die betroffene Variable zuzugreifen oder diese zu modifizieren.
Wenn Sie einige Artikel von Herb Sutter (besonders diejenigen, die sich mit Thread-Synchronisation in seiner "Guru der Woche" -Serie beschäftigen) nachschlagen, finden Sie detaillierte Erklärungen, warum das so ist. Andere Autoren beschreiben auch solche Dinge, aber Sutters Artikel sind solche, an die ich mich spontan erinnere.
Die Lösung besteht darin, einige Mittel der Synchronisation einzuführen, und für jeden Thread in Ihrem Programm, es religiös zu verwenden, bevor Sie auf Variablen zugreifen oder diese ändern. Dies vermeidet die verschiedenen Probleme (Rassenzustände, Operationen, die teilweise durchschritten werden), die Symptome verursachen würden, die Sie beschreiben.
Solche Probleme werden selten von einem Debugger durchbrochen. Der Grund dafür ist, dass die Symptome eine emergente Eigenschaft sind. Mehrere unwahrscheinliche und oft unabhängige Ereignisse in verschiedenen Ausführungsthreads müssen zusammen auftreten. Debugger ändern in der Regel das Timing von Ereignissen in Programmen, und Timing ist eine kritische Überlegung bei den auftretenden Symptomen.
Optionen beinhalten die Schlüsselvariablen atomar (so dass bestimmte Operationen nicht verhindert werden können), kritische Abschnitte (wo die Threads explizit innerhalb eines Programms kooperieren) oder Mutexe (die je nach Definition Threads in verschiedenen Programmen erlauben, zuvor explizit zusammenzuarbeiten) Zugriff auf Shared Memory).
Ja, dies führt zu einem Flaschenhals in Ihrem Programm - ein Punkt, wo jeder Thread Rendezvous und potenziell aufeinander warten muss. Dies kann den Durchsatz Ihres Programms beeinflussen. Einige Leute befürworten die Verwendung von volatilen Variablen, um solche Bedenken zu vermeiden. Meistens sind intermittierende Symptome in lang laufenden Programmen, wie Sie sie in dieser Frage beschrieben haben, und der "ähnlichen Frage", zu der Sie verlinkt sind, die Folge.
Es spielt keine Rolle, ob Sie Standardmittel der Synchronisation (z. B. in C++ 11 eingeführt) oder Windows-spezifische Mittel (WIN API-Funktionen) verwenden. Wichtig ist, dass Sie eine absichtliche Synchronisationsmethode verwenden, anstatt Variablen nur flüchtig zu machen. Verschiedene Optionen für die Synchronisierung haben unterschiedliche Kompromisse, so dass Sie eine Entscheidung treffen müssen, die für die Bedürfnisse Ihres Programms relevant ist.
Eine weitere Überlegung ist, alle Threads zu signalisieren, damit sie sauber schließen, warten, bis sie alle geschlossen sind, ihre Exit-Codes erfassen und DANN das Programm beenden. Es ist oft weniger fehleranfällig, dies in dem Thread zu tun, der main() ausführt - was letztendlich den Prozess startet, so dass er wahrscheinlich Zugriff auf Informationen hat, die er richtig bereinigen muss. Wenn ein anderer Thread entscheidet, dass das Programm beendet werden muss, dann ist es besser, wenn er diesen Bedarf zurück zu main() kommuniziert, um dies zu tun.
UB irgendwo? Stack überschrieben? Nichts anderes scheint seltsam oder fehl am Platz? –
@JoachimPileborg ist es schwer zu sagen. Es kann irgendwo UB geben, aber wie könnte ich es finden? Der Stapel scheint in Ordnung zu sein. Ich verstehe UB ist UB, aber in diesem Fall müssen wir nicht einmal durch Regeln von C++ gebunden sein. WINAPI-Funktionen wie 'ExitProcess' sollten einfach funktionieren (wir können sie aus einem Assemblerprogramm aufrufen). Ich bin absolut sicher, dass "1" an "exit" übergeben wird, weil ich den Standard-Bibliothekscode im Debugger sehen und tatsächlich mit dem Debugger beobachten kann, wie 'ExitProcess (1)' aufgerufen wird. sogar auf der Maschinenbefehlsebene. Ich kann 'ExitProcess' jedoch nicht weiter debuggen. –