Laden...

volatile durch lock ersetzen

Erstellt von ProgrammierTroll vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.382 Views
ProgrammierTroll Themenstarter:in
107 Beiträge seit 2011
vor 12 Jahren
volatile durch lock ersetzen

Hallo,

nach dem ich mich noch mal intensiver mit dem Thema beschäftigt habe, hakt es bei mir ein wenig.

Was ich bisher dachte, was volatile bedeutet: Ich sorge dafür, dass eine Variable stets den aktuellen Wert hat. Also galt für mich immer, es ist dann geeignet, wenn ein Thread eine Variable liest und ein anderer diese schreibt.

Klingt erst mal ganz plausibel, oder? Nee, alles Quatsch.

Ich habe mich dann eines besseren belehrt und bin zu dem Schluss gekommen, mir die Best Practice Regel aufzuerlegen: "Favour lock over volatile".

Nun dieses Beispiel, dass ich von der Thread-Page habe:

        
static void Main(string[] args)
        {
            bool complete = false;

            var t = new Thread(() =>
            {
                bool toggle = false;
                
                while (!complete)
                {
                    toggle = !toggle;
                }
            });
            
            t.Start();
            Thread.Sleep(1000);
            complete = true;

            t.Join();         
        }
 

Völlig einleuchtend ist, dass dieses Programm niemals terminiert, wenn ich kein volatile verwende.

Aber wo und warum setze ich nun den Lock? Das bereitet mir irgendwie Kopfzerbrechen.

Danke!

q.e.d.

2 Beiträge seit 2006
vor 12 Jahren

Hi!

lock kannst du in dem Fall nur einsetzen, wenn du complete zu einer Eigenschaft machst. Ansonsten kriegt du das lock gar nicht unter.

In deinem Beispiel macht IMHO volatile aber durchaus Sinn.

Cheers,
Nessi

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo ProgrammierTroll,

für mich ist das kein "entweder oder" sondern das sind zwei unterschiedliche Sprachmittel, die jeweils ihren eigenen Zweck haben. lock verhindert den gleichzeitigen Zugriff (und damit potenzielle Inkonsistenzen bei nicht atomaren Typen), wogegen volatile dafür sorgt, dass der jeweils aktuelle Wert einer Variablen verwendet wird, auch dann, wenn er der Wert im Cache des eigenen Threads steht und er durch einen anderen Thread im Hauptspeicher geändert wurde. Da beides mit Synchronisation zu tun hat, ergänzt sich beides, mehr aber auch nicht.

Das Problem terminiert bei mir auch ohne volatile. Wenn eine Variable nicht als volatile deklariert ist, bedeutet das nicht, dass nicht zwangsläufig, dass nie der richtige Wert geliefert wird, sondern nur, dass nicht sichergestellt ist, dass bei jedem Zugriff immer der aktuellste Wert verwendet wird.

Hallo BrucklynBoy,

lock kannst du in dem Fall nur einsetzen, wenn du complete zu einer Eigenschaft machst. Ansonsten kriegt du das lock gar nicht unter.

das ist Unsinn. Natürlich kann man kann lock auch bei einfachen Variablen einsetzen. Man muss nur an allen Stellen, an denen zugegeriffen wird, dasselbe lock-Objekt verwenden.

herbivore

ProgrammierTroll Themenstarter:in
107 Beiträge seit 2011
vor 12 Jahren

für mich ist das kein "entweder oder" sondern das sind zwei unterschiedliche Sprachmittel, die jeweils ihren eigenen Zweck haben. lock verhindert den gleichzeitigen Zugriff (und damit potenzielle Inkonsistenzen bei nicht atomaren Typen), wogegen volatile dafür sorgt, dass der jeweils aktuelle Wert einer Variablen verwendet wird, auch dann, wenn er der Wert im Cache des eigenen Threads steht und er durch einen anderen Thread im Hauptspeicher geändert wurde. Da beides mit Synchronisation zu tun hat, ergänzt sich beides, mehr aber auch nicht.

Das dachte ich auch immer, ist aber so nicht richtig.

Das Problem terminiert bei mir auch ohne volatile. Wenn eine Variable nicht als volatile deklariert ist, bedeutet das nicht, dass nicht zwangsläufig, dass nie der richtige Wert geliefert wird, sondern nur, dass nicht sichergestellt ist, dass bei jedem Zugriff immer der aktuellste Wert verwendet wird.

Man muss das Programm im als Release ohne Debug ausführen, damit die Optimierung zur Geltung kommt.

PS: Wenn ich das lock in das Innere der While-Schleife setze, terminiert das Programm. Allerdings weiß ich nicht, warum.

q.e.d.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo ProgrammierTroll,

Das dachte ich auch immer, ist aber so nicht richtig.

klar ist das richtig. Du kannst gerne in die Doku schauen. Wieso bist du da anderer Meinung?

Man muss das Programm im als Release ohne Debug ausführen

Das habe ich getan. Bei mir terminiert es.

Wenn ich das lock in das Innere der While-Schleife setze, terminiert das Programm

Wenn die Runtime schlau ist (und das hoffen wir mal), wird sie alle (gemeinsamen) Variablen, auf die innerhalb der lock-Blöcke zugegriffen wird, implizit als volatile betrachten.

herbivore

2 Beiträge seit 2006
vor 12 Jahren

Hallo herbivore,

lock kannst du in dem Fall nur einsetzen, wenn du complete zu einer Eigenschaft machst. Ansonsten kriegt du das lock gar nicht unter.
das ist Unsinn. Natürlich kann man kann lock auch bei einfachen Variablen einsetzen. Man muss nur an allen Stellen, an denen zugegeriffen wird, dasselbe lock-Objekt verwenden. Wenn du natuerlich bei jedem Zugriff auf eine Variable dein lock drum rum baust, dann kannst du das selbstverstaendlich so tun. ALlerdings habe ich 'einen Schritt' weiter gedacht, versucht, Code Redundanz zu vermeiden und die Variable geistig schon in eine Eigenschaft gekapselt. Mein Fehler 😉

Cheers,
Nessi

P.S.: Ist es ueblich, Beitraege, die aus Admin-Sicht falsch sind, dann gleich zu editieren statt im Original zu belassen und 'nur' zu kommentieren?

Hinweis von herbivore vor 12 Jahren

Nur wenn sie eindeutig belegbar falsch sind, aber gleichzeitig geeignet, gerade Anfänger auf die falsche Spur zu führen.

ProgrammierTroll Themenstarter:in
107 Beiträge seit 2011
vor 12 Jahren

klar ist das richtig. Du kannst gerne in die Doku schauen. Wieso bist du da anderer Meinung?

Ich beziehe mich auf:

The MSDN documentation states that use of the volatile keyword ensures that the most up-to-date value is present in the field at all times. This is incorrect, since as we’ve seen, a write followed by a read can be reordered.
...

Threading in C#: volatile

Das habe ich getan. Bei mir terminiert es.

Dann bedingt das wahrscheinlich unsere (vermutlich) unterschiedliche Hardware.

Wenn die Runtime schlau ist (und das hoffen wir mal), wird sie alle (gemeinsamen) Variablen, auf die innerhalb der lock-Blöcke zugegriffen wird, implizit als volatile betrachten.

Findet Zugriff erst dann statt, wäre das eine plausible Erklärung.

q.e.d.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo ProgrammierTroll,

ok, das Beispiel von Joseph Albahari bzw. im Original von Joe Duffy ist verblüffend. Mir war bisher nicht klar, dass volatile mit "half-fences" arbeitet. Die Doku ist also nicht 100%ig akkurat, was natürlich blöd ist.

Doch wirklich was ändern tut das aus meiner Sicht nichts. In dem verblüffenden Beispiel kann - und wird meistens auch - eine von beiden volatilen Variablen 0 bleiben, selbst wenn volatile mit full-fences arbeiten würde. Mit anderen Worten, selbst wenn sichergestellt wäre, dass durch volatile zwei nahezu gleichzeitige Read/Write bzw. Write/Read-Operationen nie geswappt werden würden, es ist ja nicht mal sichergestellt, dass die Operationen überhaupt nahezu gleichzeitig laufen. Es könnte genauso gut sein, dass erst der erste Thread läuft und erst wenn er fertig ist, der zweite mit der Arbeit beginnen. Damit würde das erhoffe Write auf y lange nach dem Read auf y ausgeführt werden. Dass muss man ja bei volatile immer im Blick haben. Man kann also (ohne weitere Synchronisation) eh nicht davon ausgehen, dass ein bestimmtes Write schon vor dem Read ausgeführt wurde. Man kann also in dem Sinne sowieso nicht davon ausgehen, dass der Wert der Variablen so aktuell ist, wie man es gerne hätte. Ob man nun einen nicht 100%ig aktuellen Wert bekommt, weil zwei quasi gleichzeitige Operationen geswappt werden oder weil die Write-Operation aufgrund der zufälligen Verzahnung der Threads einfach ein paar Nanosekunden später ausgeführt wird, macht in der Praxis vermutlich keinen Unterschied.

Insofern sehe ich nicht, dass sich durch Kenntnis des verblüffenden Beispiels etwas grundsätzlich an der üblichen Verwendung von volatile geändert hat.

herbivore

ProgrammierTroll Themenstarter:in
107 Beiträge seit 2011
vor 12 Jahren

Insofern sehe ich nicht, dass sich durch Kenntnis des verblüffenden Beispiels etwas grundsätzlich an der üblichen Verwendung von volatile geändert hat.

Da hast du nicht ganz unrecht. Vielleicht habe ich mich auch zu sehr durch die Ausführungen verunsichern lassen. Er schreibt ja:

This presents a strong case for avoiding volatile: even if you understand the subtlety in this example, will other developers working on your code also understand it?

Für mich klang das so wie die Vererbungswarnung oder das Nicht-Verwenden von Go-To und seinen Derivaten. Ich habe durchaus in meinen Projekten mit Entwicklern zu tun, die noch weniger Ahnung haben als (ja, gibt es g) und da wäre so eine Regel wie "verwende besser locks anstatt volatile und du bist immer auf der sicheren Seite" eine schöne Sache gewesen. Die Welt ist leider und natürlich nicht so einfach und wie du schon richtig schriebst, verfolgen beide Konzepte unterschiedliche Aspekte der Synchronisierung.

q.e.d.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo ProgrammierTroll,

Synchronisation ist ein schwieriges Geschäft. Das habe ich schon verschiedentlich im Forum geschrieben und dazu stehe ich. Und ich will hier keineswegs den Eindruck erwecken, dass man nicht genau wissen muss, was man tut. Gerade bei Synchronisierung muss man das sehr genau wissen. Testen hilft da leider oft nicht, um Korrektheit sicherzustellen, wie man schon an deinem Beispiel oben sieht, das auf meinem Rechner terminiert, aber auf deinem nicht.

Insofern möchte ich meinen vorigen Beitrag nicht als "man muss sich keine Sorgen machen, wird schon klappen" verstanden wissen. Im Gegenteil, man muss sehr genau wissen, was volatile leistet und was nicht, um es richtig einsetzen zu können. Gerade darauf wollte ich hinaus: Man kann sich bei volatile nicht darauf verlassen, dass der Wert so aktuell ist, wie man es gerne hätte. Wenn man volatile 1:1 durch lock ersetzen würde, wäre das übrigens nicht anders. Wenn man will, dass eine Aktion erst ausgeführt wird, wenn eine Variable einen bestimmten Wert hat, kommt man mit lock und volatile (alleine) nicht weiter, eher schon mit EventHandle (siehe Consolenanwendung mit Timer optimieren [Semaphore/Ampel]).

Wenn man allerdings genau weiß, was volatile leisten kann und was eben nicht, dann spricht aus meiner Sicht nichts gegen einen Einsatz, zumindest wenn man Situationen, wie in dem verblüffenden Beispiel vermeidet. Wenn man eine Lehre aus dem verblüffenden Beispiel ziehen will, dann die, dass man solche unsinnigen Situationen vermeiden muss. 😃

In dem Beispiel von dir ganz oben, würde volatile problemlos seinen Job machen.

herbivore

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo,

weil zum Thema passt und das MemoryModel erklärt - und dieses ist zuständig für volatile - ein paar Links inden auf dieses eingegangen wird:
Volatile keyword in C# – memory model explained
Understanding Low-Lock Techniques in Multithreaded Apps (v.a. der obere Teil)
Memory Model (ist zwar schon älter, beschreibt aber die Hintergrünge ganz gut).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"