volatile



  • Dass volatile raisconditions nicht verhindern kann ist mir klar, dass war aber überhaupt nicht meine Frage - ich habe mich vielleicht auch unklar ausgedrückt.
    (C++98) habe ich nur geschrieben, dass nicht sowas wie: benutzt doch einfach std::atomic kommt. Und was versuchst du mir mit dem Satz, dass es keine Threads in C++98 gibt zu vermitteln?

    Mir geht es um die while. Darf der Compiler hier abort im Register halten, oder muss er abort nach jedem Durchlauf aus dem Arbeitsspeicher holen (womit ein volatile nötig wäre).



  • Meines Wissens ist das volatile genau für solche Dinge wie deine Fragestellung da,
    dass der Compiler von solchen Optimierungen wie abort in einem Register vorzuhalten
    Abstand nimmt, und stattdessen Code erzeugt, mit dem abort bei jedem Schleifendurchlauf
    aus dem Speicher gelesen wird.
    Ein weiterer Effekt von volatile dürfte bei dem gezeigten Code sein, dass für abort keine
    Konstantenfaltung mehr durchgeführt wird, die jedes Vorkommen von abort durch ein false
    ersetzt und die letzte Zuweisung ( abort = true; ) rauswirft, weil die Variable danach nicht mehr gelesen wird.

    Natürlich reicht volatile alleine nicht aus, da beispielsweise immer noch Instruktions-Reordering durch
    den Compiler und/oder die CPU gemacht werden könnte. So könnte im erzeugten/tatsächlich ausgeführten
    Code das abort = true; vor das sleep(1); wandern, was schonmal ein Fehler wäre, da dann nicht mehr
    mindestens eine Sekunde gewartet wird, bevor sich der Thread beendet. std::atomic fügt Instruktionen und
    Compiler-Hinweise ein die so etwas verhindern.

    Gruss,
    Finnegan

    P.S: Noch lustiger ist das "Reordering" natürlich wenn das abort = true; VOR das abort = false; umsortiert wird,
    und auch wenn ich mir sehr unsicher bin, ob das bei diesem speziellen Beispiel tatsächlich passieren kann, macht es glaube ich
    deutlicher, weshalb man besser std::atomic verwendet (es ist leicht Code zu schreiben bei dem solche lustigen Sachen passieren).



  • JulianH_ schrieb:

    Dass volatile raisconditions nicht verhindern kann ist mir klar, dass war aber überhaupt nicht meine Frage - ich habe mich vielleicht auch unklar ausgedrückt.
    (C++98) habe ich nur geschrieben, dass nicht sowas wie: benutzt doch einfach std::atomic kommt. Und was versuchst du mir mit dem Satz, dass es keine Threads in C++98 gibt zu vermitteln?

    ich vermute mal, dass manni damit sagen wollte, das C++98 nicht nur keine threads kennt, sondern noch dazu kein objekt-modell, dass sich um so parallele dinge schert. d.h. es gibt keinerlei zusicherungen, welche instruktionen (in zwei threads) wann und wie (synchronisiert) auf bestimmte variablen zugreifen dürfen. deshalb reicht es auch nicht (für dein beispiel), irgendeine thread library für C++98 zu verwenden, wenn du nicht die regeln deiner implementierung (compiler, library) kennst. volatile diente dann dafür, dein beispiel sicher zu machen, wenn *keine* threads mit im spiel sind. aber das hat ja Finnegan schon detaillierter beschrieben.



  • JulianH_ schrieb:

    Mir geht es um die while. Darf der Compiler hier abort im Register halten, oder muss er abort nach jedem Durchlauf aus dem Arbeitsspeicher holen (womit ein volatile nötig wäre).

    Der Compiler darf hier machen was er will, da das undefined Behavior ist. volatile oder nicht ändert daran nix. Die Probleme hier sind nicht nur potentielles reordering, sondern fangen schon dabei an, dass nichtmal sicher ist, dass beim Lesen des bool überhaupt ein valider bool Wert rauskommt. Benutz std::atomic.



  • dot schrieb:

    Der Compiler darf hier machen was er will, da das undefined Behavior ist. volatile oder nicht ändert daran nix.

    Ja, trotz meiner vorherigen Ausführungen möchte ich mich hier anschliessen. Der Standard sagt dass data races UB sind (in diesem Fall das potentiell gleichzeitige schreiben und lesen von abort ).
    In der Theorie kann kann also wirklich alles passieren. In der Praxis wird wahrscheinlich eher so äußern wie ich es in meinem Beitrag beschrieben habe.



  • Finnegan schrieb:

    P.S: Noch lustiger ist das "Reordering" natürlich wenn das abort = true; VOR das abort = false; umsortiert wird

    Müssen Globale-Konstruktoren nicht immer vor der main aufgerufen werden?

    Mir ging es im Prinzip tatsächlich nur darum, ob der Compiler Variablen welche außerhalb des aktuellen Scopes sind einfach im Register halten darf.

    Da der Compiler hier wohl optimieren darf, muss man sich natürlich situationsabhängig Gedanken machen, wie man da Fehler am besten vermeidet.
    In meinem Beispiel wollte ich nur eine while die nicht unendlich lang läuft, wofür dann ja volatile ausreicht.



  • JulianH schrieb:

    Müssen Globale-Konstruktoren nicht immer vor der main aufgerufen werden?

    Ja, aber Code, wo das zum Problem werden kann ist leicht geschrieben, wenn man unachtsam ist. Nicht umsonst ist die Default-Memory-Order bei std::atomic -Operationen auch die stärkste (auch wenn in vielen Fällen schwächere Zusicherungen ausreichen).

    JulianH schrieb:

    Mir ging es im Prinzip tatsächlich nur darum, ob der Compiler Variablen welche außerhalb des aktuellen Scopes sind einfach im Register halten darf.

    Dafür ist das Beispiel etwas unglücklich gewählt, da das vohandene data race alle Überlegungen bezüglich Register oder Speicher müßig macht. Allerdings fällt mir so auf die Schnelle auch kein gutes Beispiel ein bei dem lediglich das volatile ausreicht.
    Hardware-Programmierung, bei der z.B. ein Speicherbereich auf der Hardware in den Adressraum des Prozesses gemappt ist, ohne dass mehrere Threads im Spiel sind, bieten sicher einige Anwendungsfälle für volatile .
    Und ja, in diesem Fall werden solche Optimierungen wie Variable nur im Register mit volatile verhindert. Ich selbst habe volatile bisher nur als simplen Trick verwendet um einen Algorithmus mit hardcodierten Daten zu testen
    (Da kann es nämlich schonmal schnell passieren dass wenn alle Daten aus de-facto-Konstanten stammen mein Algorithmus direkt vom Compiler ausgeführt wird, und dann nur das Endergebnis im kompilierten Programm landet).

    Ich denke es ist zwar gut zu wissen, was volatile macht, aber dann sollte man es erstmal vergessen, besonders wenn es lediglich um Thread-Synchronisation geht. Wenn man dann tatsächlich mal irgendwelchen Hardware-Kram macht,
    und/oder andere Fälle hat wo es sinnvoll einzusetzen ist kann man sich gerne wieder dran erinnern, dass es volatile gibt.

    Finnegan



  • Ich habe ein Beispiel wo volatile unabdingbar ist (allerdings ohne Threads):

    #include <csignal>
    
    static std::sig_atomic_t volatile running = 1;
    
    static void handler(int)
    {
        running = 0;
    }
    
    int main()
    {
        std::signal(SIGTERM, &handler);
        std::signal(SIGINT, &handler);
    
        while (running)
        {
            // Tu was
        }
    }
    

    Unter verschiedenen Compilern/Stdlibs bekam ich hier damals ohne volatile manchmal eine Endlosschleife.


  • Mod

    Biolunar schrieb:

    Ich habe ein Beispiel wo volatile unabdingbar ist (allerdings ohne Threads):

    Unter verschiedenen Compilern/Stdlibs bekam ich hier damals ohne volatile manchmal eine Endlosschleife.

    Zurecht, denn der Standard verlangt schon immer volatile für diesen speziellen Zweck.



  • Was genau bezweckt denn eigentlich, dass running 1 bleibt, trotz Signal Handler?


Anmelden zum Antworten