Frage

Einige Sprachen bieten einen volatile Modifikator, wie die Durchführung einer „Lesespeicherbarriere“ vor dem Lesen des Speichers, dass Rücken eine Variable beschrieben wird.

Eine Lesespeicherbarriere allgemein als eine Möglichkeit beschrieben wird, um sicherzustellen, dass die CPU die liest angefordert vor der Schranke durchgeführt hat, bevor sie eine nach der Sperre angefordert führen lesen. Um jedoch diese Definition verwendet, würde es scheinen, dass ein schaler Wert noch gelesen werden kann. Mit anderen Worten liest die Durchführung in einer bestimmten Reihenfolge nicht zu bedeuten scheint, dass der Hauptspeicher oder andere CPUs muß sicherstellen, dass nachfolgende Werte lesen spiegeln tatsächlich die neuesten im System zum Zeitpunkt der Lesesperre oder geschrieben anschließend nach dem, um sicherzustellen, zu Rate gezogen werden Lesesperre.

Das Gleiche gilt, volatile wirklich garantiert werden, dass ein up-to-date-Wert gelesen oder gerade (Keuchen!), Dass die Werte, die gelesen werden, zumindest sind up-to-date wie die liest vor der Schranke? Oder eine andere Interpretation? Was sind die praktischen Auswirkungen dieser Antwort?

War es hilfreich?

Lösung

Es gibt Lesesperren und Schreibsperren; acquire Barrieren und die Freisetzung Barrieren. Und mehr (io vs Speicher usw.).

Die Barrieren gibt es nicht „latest“ Wert oder „Frische“ zu steuern, der Werte. Sie sind da, um die relative Anordnung der Speicherzugriffe zu steuern.

Schreibbarrieren steuern die Reihenfolge der Schreibvorgänge. Da schreibt in den Speichern langsam ist (im Vergleich zu der Geschwindigkeit der CPU), ist es in der Regel eine Schreibanforderungswarteschlange, wo schreibt geschrieben werden, bevor sie ‚wirklich geschehen‘. Obwohl sie, um die Warteschlange gestellt werden, während die Schreibvorgänge in der Warteschlange neu angeordnet werden können. (Also vielleicht ‚Warteschlange‘ ist nicht der beste Name ...) Es sei denn, Sie Schreibsperren verwenden, um die Neuanordnung zu verhindern.

Lesen Barrieren steuern die Reihenfolge der liest. Aufgrund der spekulativen Ausführung (CPU schaut nach vorn und Lasten aus dem Gedächtnis zu früh) und wegen der Existenz des Schreibpuffers (die CPU einen Wert aus dem Schreibpuffer anstelle des Speichers lesen, wenn es da ist - dh die CPU denkt, dass es gerade geschrieben habe X = 5, dann, warum es wieder lesen, nur sehen, dass es noch wartet wird 5 im Schreibpuffer) liest der Ordnung geschehen geführt werden.

Dies gilt unabhängig davon, was der Compiler versucht, in Bezug auf die Reihenfolge des generierten Codes zu tun. dh ‚volatile‘ in C ++ wird hier nicht helfen, weil es nur die Compiler Ausgangscode neu zu lesen, den Wert von „Gedächtnis“ sagt, es nicht der CPU nicht sagen, wie / wo es aus (dh „Gedächtnis“ zu lesen viele Dinge auf CPU-Ebene ist).

So Lese- / Schreibsperren setzten Blöcke Neuordnen in den Lese- / Schreib-Warteschlangen zu vermeiden (die Lese der Regel nicht so viel von einer Schlange, aber die Neuordnungs Effekte sind gleich).

Welche Arten von Blöcken? -. Acquire und / oder Freisetzung blockiert

Acquire - zB Lese-acquire (x) wird die Lese von x in die Lese-Warteschlange hinzufügen und spülen Sie die Warteschlange (wirklich nicht in die Warteschlange spülen, sondern eine Markierung hinzufügen, sagt nicht Neuordnungs etwas vor diesem zu lesen, die als ob die Warteschlange war gerötet). So später (in Code Reihenfolge) liest neu geordnet werden kann, aber nicht vor dem Lesen von x.

Version - zB Schreibrelease (x, 5) spült (oder Markierung) die Warteschlange zuerst, dann fügen Sie die Schreibanforderung in die Schreibwarteschlange. Also früher schreibt nicht geworden neu geordnet geschehen nach x = 5, aber beachten Sie, dass später schreibt, bevor x = 5 erst nachbestellt werden.

Beachten Sie, dass ich die Lese mit Belegungs- und Schreib mit Release gepaart, weil dies typisch ist, sondern verschiedene Kombinationen sind möglich.

Acquire und Freigabe werden als ‚Halbschranken‘ oder ‚Halb Zäune‘, weil sie nur aus geht ein Weg, um die Umordnung stoppen.

Eine vollständige Barriere (oder Voll Zaun) gilt sowohl ein Belegungs und Release - dh keine Nachbestellung.

Typisch für lockfree Programmierung oder C # oder Java 'volatile', was Sie wollen / müssen ist Lese-acquire und Schreib Release.

dh

void threadA()
{
   foo->x = 10;
   foo->y = 11;
   foo->z = 12;
   write_release(foo->ready, true);
   bar = 13;
}
void threadB()
{
   w = some_global;
   ready = read_acquire(foo->ready);
   if (ready)
   {
      q = w * foo->x * foo->y * foo->z;
   }
   else
       calculate_pi();
}

Also, zunächst einmal ist dies ein schlechter Weg, um Programm-Threads. Schlösser wäre sicherer. Aber nur Hindernisse zu illustrieren ...

Nach ThreadA () getan Schreiben foo ist, muss es schreiben foo-> bereit LESEN, wirklich letzte, sonst andere Threads könnten sehen foo-> schon früh fertig und die falschen Werte von x / y / z erhalten. Also haben wir einen write_release auf foo-> bereit zu verwenden, die, wie oben erwähnt, effektiv ‚spült‘ die Schreibwarteschlange (um sicherzustellen, x, y, z begangen werden) fügt dann die bereit = true Anforderung in die Warteschlange. Und dann fügt die bar = 13 Anfrage. Beachten Sie, dass, da wir nur eine Release-Schranke (kein Voll) bar = 13 kann vor betriebsbereit geschrieben werden. Aber wir kümmern uns nicht! dh wir davon bar nicht gemeinsam genutzte Daten zu ändern.

Jetzt ThreadB () muss wissen, dass, wenn wir sagen ‚bereit‘ wir wirklich meinen bereit. Also haben wir eine read_acquire(foo->ready) tun. Diese Lese wird an die Lesewarteschlange hinzugefügt, dann ist die Warteschlange geleert. Beachten Sie, dass w = some_global kann nach wie vor auch in der Warteschlange sein. So foo-> fertig gelesen werden können vor some_global. Aber auch hier ist es uns egal, da es nicht Teil der wichtigen Daten ist, dass wir sind, so vorsichtig. Waswir kümmern uns um ist foo-> x / y / z. So dass sie in die Lesewarteschlange nach dem acquire bündig / Marker hinzugefügt werden, zu gewährleisten, dass sie nur nach dem Lesen foo- gelesen werden> bereit.

Beachten Sie auch, dass dies in der Regel genau die gleichen Schranken zum Verriegeln und Entriegeln eines Mutex / Critical / etc. (Dh acquire auf Schloss (), Freigabe auf Unlock ()).

So

  • Ich bin mir ziemlich sicher, dass dies (das heißt acquire / release) ist genau das, was MS docs sagen geschieht zum Lesen / schreibt von 'flüchtigen' Variablen in C # (und optional für MS C ++, aber dies ist nicht-Standard) . Siehe http://msdn.microsoft.com/en-us /library/aa645755(VS.71).aspx darunter „Ein flüchtiger Lese-‚acquire Semantik‘, das heißt, es ist garantiert vor allen Verweisen auf Speicher auftreten, nachdem sie auftreten ...“

  • I denkt Java ist das gleiche, obwohl ich nicht so vertraut bin. Ich vermute, es ist genau die gleiche, weil Sie gerade die Regel nicht mehr Garantien benötigen, als Lese-acquire / Schreib-Release.

  • In Ihrer Frage, die Sie auf dem richtigen Weg waren, als zu denken, dass es ist wirklich alles über relative Ordnung - man mußte nur die Anordnungen nach hinten (dh „die Werte, die als up-to-date sind zumindest gelesen werden als das liest vor der Schranke "-. nein, liest vor der Schranke sind unwichtig, seine liest nACH der Barriere, die nach dem umgekehrt für Schreibvorgänge) sind garantiert sein

  • Und bitte beachten Sie, wie bereits erwähnt, geschieht Neuordnen auf Lese- und Schreibvorgänge, so dass nur eine Barriere auf einem Thread und nicht mit der anderen nicht funktionieren. dh ein Schreibrelease ist nicht genug, ohne die Lese-acquire. dh auch wenn Sie es in der richtigen Reihenfolge zu schreiben, könnte es in der falschen Reihenfolge gelesen werden, wenn Sie nicht die Lesesperren verwendet haben mit den Schreibbarrieren gehen.

  • Und schließlich beachten Sie, dass Lock-freie Programmierung und CPU-Speicher-Architekturen eigentlich viel komplizierter als das sein können, aber Kleben mit acquire / Release wird man ziemlich weit.

Andere Tipps

volatile in den meisten Programmiersprachen impliziert keine reale CPU Lesespeicherbarriere aber einen Auftrag an den Compiler nicht liest über Caching in einem Register zu optimieren. Dies bedeutet, dass der Leseprozess / Thread den Wert „schließlich“ bekommen. Eine übliche Technik ist ein boolescher volatile Flag zu erklären in einem Signalbehandlungsroutine und überprüft in der Hauptprogrammschleife gesetzt wird.
Im Gegensatz CPU sind Speicherbarrieren entweder direkt vorgesehen, über CPU-Instruktionen oder implizierten mit bestimmten Assembler Mnemonics (wie lock Präfix in x86) und werden zum Beispiel verwendet, wenn auf dem Hardware-Geräte sprechen, wo die Reihenfolge der Lese- und Schreibvorgänge in dem Speicher-Mapped IO-Registers ist wichtig, oder Synchronisieren von Speicherzugriff in Multi-Processing-Umgebung.
Um Ihre Frage zu beantworten - nein, nicht Gedächtnis Barriere nicht garantieren „latest“ Wert, aber Garantien um von Speicherzugriffsoperationen. Dies ist entscheidend, zum Beispiel in Lock-frei Programmierung.
hier ist einer der Primer auf der CPU-Speicherbarrieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top