Sicher, volatile bool zu verwenden, um einen anderen Thread zum Warten zu zwingen? (C ++)

8

Alles, was ich über volatile gelesen habe, sagt, dass es nie sicher ist, aber ich fühle mich immer noch geneigt, es zu versuchen, und ich habe dieses spezifische Szenario, das als unsicher erklärt wurde, nicht gesehen.

Ich habe einen separaten Thread, der eine Szene rendert und Daten aus dem Hauptsimulations-Thread zieht. Dies hat keine Synchronisation und funktioniert gut.

Das Problem besteht darin, dass der Renderer beim Beenden des Programms aufhört, Daten aus dem Simulationsthread zu ziehen, bevor sich der Simulationsthread sicher selbst aufräumen kann, ohne dass der Renderer versucht, ungültigen Speicher zu lesen.

Um dies zu erreichen, lasse ich den Renderer unendlich in seinem Thread laufen:

%Vor%

Wenn im Hauptprogramm-Thread die windproc-Beendigungsnachricht empfangen wird, mache ich:

%Vor%

Damit soll sichergestellt werden, dass der Renderer keine Daten mehr aus der Anwendung zieht, bevor er in der Anwendung löscht.

Ich habe das zuerst ohne volatile Schlüsselwörter versucht, und es funktionierte im Debug-Modus, aber im Release-Modus blieb es hängen. Ich nehme an, der Compiler hat eine Optimierung vorgenommen, die dazu führt, dass das Programm den Wert von stillRendering nicht mehr überprüft.

Das Hinzufügen von volatile zu nur stillRendering führte dazu, dass die Anwendung jedes Mal, wenn ich sie getestet habe, erfolgreich beendet wurde. Ich bin mir nicht sicher, warum es keine Rolle spielt, ob "programRunning" flüchtig ist.

Schließlich bin ich unsicher, wie die Leistung des Programms durch die Verwendung von volatile für "stillRendering" beeinflusst wird. Es ist mir egal, ob sich die Ausführung von "stillRendering volatile" auf die Leistung von OnQuit () auswirkt, aber es macht mir etwas aus, wenn es die Leistung von RenderThreadFunction () beeinflusst

    
Matt Swarthout 25.02.2013, 18:41
quelle

5 Antworten

8

Es ist völlig unsicher, obwohl es mit einigen funktionieren könnte Compiler. Grundsätzlich wirkt volatile nur auf die Variable, die es ist angehängt an, also könnte RendererThreadFunction zum Beispiel gesetzt werden stillRendering false vor beendet %Code%. (Dies gilt auch, wenn beide renderer->render(); und stillRendering waren beide flüchtig.) Die Die Wahrscheinlichkeit eines Problems ist sehr gering, also wahrscheinlich testen wird es nicht verraten. Und schließlich geben einige Versionen von VC ++ do programRunning die Semantik eines atomaren Zugriffs unter C ++ 11, in In diesem Fall wird Ihr Code funktionieren. (Bis zum Kompilieren mit eine andere Version von VC ++, natürlich.)

Vorausgesetzt, dass volatile fast sicher dauert eine nicht zu vernachlässigende Menge an Zeit, es gibt absolut keinen Grund dafür, hier keine bedingte Variable zu verwenden. Über die einzige Zeit Sie würden renderer->render() für diese Art von Sache verwenden, wenn das Herunterfahren Mechanismus wurde durch ein Signal ausgelöst (in diesem Fall der Typ wäre volatile und nicht sig_atomic_t , obwohl in der Praxis es macht wahrscheinlich keinen Unterschied). In diesem Fall, dort wären nicht zwei Threads, sondern nur der Renderer-Thread und ein Signalhandler.

    
James Kanze 25.02.2013, 19:28
quelle
6

Wenn Sie möchten, dass Ihr Code auf allen Architekturen in allen Compilern funktioniert, verwenden Sie C ++ 11 atomics:

%Vor%

Volatile ist nicht zur Verwendung mit Multi-Threading ausgelegt - Compiler sind vom Standard tatsächlich dazu berechtigt, volatile -Zugriffe mit nicht volatile -Zugriffen neu zu ordnen. VC ++ erweitert das Feature-Set von volatile , um Neuanordnungen zu verhindern, andere Compiler dagegen nicht, und es könnte bei diesen Compilern brechen.

Wie andere bereits erwähnt haben, wirkt sich volatile auch nicht auf die Sichtbarkeit aus, was bedeutet, dass Architekturen, die nicht cache-kohärent sind, niemals das Flag gesetzt sehen. x86 ist nicht einmal sofort cache-kohärent (Schreibvorgänge wären extrem langsam), so dass Ihr Programm immer mehr Schleifen bekommt, als es sollte, während der Schreibvorgang durch verschiedene Puffer gesendet wird.

C ++ 11 Atomics vermeiden beide Probleme.

OK, das sollte hauptsächlich dazu dienen, Ihren derzeitigen Code zu korrigieren und Sie davor warnen, volatile zu missbrauchen. James 'Vorschlag, eine Zustandsvariable (die nur eine effizientere Version dessen ist, was du tust) zu verwenden, ist wahrscheinlich die beste Lösung für dich.

    
Cory Nelson 25.02.2013 19:24
quelle
4

Es gibt drei Probleme, die C ++ 11-Atome adressieren.

Erstens kann ein Thread-Schalter mitten beim Lesen oder Schreiben eines Wertes auftreten; Bei Lesevorgängen könnte ein anderer Thread den Wert aktualisieren, bevor der ursprüngliche Thread den Rest des Werts liest; zum Schreiben könnte ein anderer Thread den halbgeschriebenen Wert sehen. Dies wird als "Reißen" bezeichnet.

Zweitens hat in einem typischen Mehrprozessorsystem jeder Prozessor seinen eigenen Cache und liest und schreibt Werte in diesen Cache; manchmal werden der Cache und der Hauptspeicher aktualisiert, um sicherzustellen, dass sie die gleichen Werte enthalten, aber bis ein Prozessor, der einen neuen Wert schreibt, seinen Cache löscht und ein Thread, der den Wert liest, seine Kopie aus dem Cache lädt, kann der Wert unterschiedlich sein. Dies wird als "Cache-Kohärenz" bezeichnet.

Drittens kann der Compiler Code herum bewegen und einen Wert speichern, bevor er einen anderen speichert, auch wenn der Code in umgekehrter Reihenfolge geschrieben wird. Solange Sie kein gültiges Programm schreiben können, das den Unterschied sehen kann, ist das unter der "Als-ob" -Regel in Ordnung.

Das Laden von und Speichern in einer atomaren Variablen (mit der Standard-Speicherreihenfolge) verhindert alle drei Probleme. Eine Variable als volatile wird nicht markiert.

EDIT: Mach dir keine Gedanken darüber, welche Architekturen welche Probleme darstellen. Der Autor der Standardbibliothek hat dies bereits für die Architektur getan, für die die Bibliotheksimplementierung vorgesehen ist. Suche nicht nach Abkürzungen. benutze einfach Atomics. Sie werden nichts verlieren.

    
Pete Becker 25.02.2013 20:45
quelle
2

Abhängig von der Architektur, die cache-kohärent ist (z. B. x86-Prozessoren), erwarte ich, dass dies gut funktioniert. Möglicherweise stellen Sie fest, dass einer Ihrer beiden Threads möglicherweise für eine Iteration mehr ausgeführt wird, als wenn Sie echte atomare Operationen verwenden. Da jedoch nur eine Seite festgelegt und die Werte nicht gelesen werden, sind keine echten atomaren Operationen erforderlich.

Wenn jedoch die Prozessoren (Kerne), die den Code ausführen, spezielle Cache-Flushing-Vorgänge benötigen, um den "anderen Kern" den aktualisierten Wert anzeigen zu lassen, können Sie für einige Zeit stecken bleiben - und Sie müssten ordnungsgemäß atomare Updates, um sicherzustellen, dass der Cache des anderen Prozessors ungültig ist.

Ich nehme an, dass renderer->render() ziemlich viel Zeit in Anspruch nimmt, also sollte das Lesen von stillRendering die Gesamtlaufzeit nicht wesentlich beeinflussen. volatile bedeutet typischerweise nur "bitte nicht in ein Register eintragen und dort aufbewahren".

(Wahrscheinlich benötigen Sie programRunning auch volatile !)

    
Mats Petersson 25.02.2013 18:53
quelle
1
  

Das Hinzufügen von volatile zu nur stillRendering führte dazu, dass die Anwendung jedes Mal erfolgreich beendet wurde, wenn ich sie getestet habe

Ja, Ihr Szenario wird funktionieren.

Der häufige Fehler bei der Verwendung von volatile Variablen für die Thread-Synchronisierung liegt vor, wenn Operationen auf volatile Variablen als atomar angenommen werden. Sie sind nicht.

In Ihrem Fall wird ein einzelner Bool abgefragt, der darauf wartet, dass er genau einmal genau auf 0 wechselt. Sie scheinen keine Operation als atomar zu erwarten. Auf der anderen Seite, auch wenn Sie eine einzelne int abfragen, wird C ++ nicht garantieren, dass ein Thread, der den int ändert, dies atomar macht.

  

Ich bin mir nicht sicher, warum es keine Rolle spielt, ob "programRunning" flüchtig ist.

Es ist wichtig. Machen Sie volatile .

Wenn Sie eine Variable volatile erstellen, wird sichergestellt, dass bestimmte Cache-Optimierungen vermieden werden, was Sie wollen.

Das bedeutet nicht, dass Sie garantiert dieselben Cache-Optimierungen haben, wenn eine Variable nicht flüchtig ist. Sie lassen den Compiler einfach entscheiden. Und zu diesem Zeitpunkt trifft der Compiler zufällig eine Entscheidung, die für Sie funktioniert.

  

Schließlich bin ich unsicher, wie die Leistung des Programms durch die Verwendung von volatile für "stillRendering" beeinflusst wird.

Ihre Leistung wird dadurch wahrscheinlich negativ beeinflusst:

%Vor%

Sie fordern einen Thread (vielleicht einen ganzen CPU-Kern) auf, endlos ohne Pause eine einzelne Variable zu lesen.

Erwägen Sie, in dieser While-Schleife einen Schlafanruf hinzuzufügen.

    
Drew Dormann 25.02.2013 19:03
quelle