Frage

Ich habe eine Multi-R / W-Sperre Klasse, die die Lese hält, schreiben und anstehende Lese, anstehende Schreibzähler. Ein Mutex schützt sie von mehreren Threads.

Meine Frage ist, Brauchen wir noch die Zähler als flüchtig erklärt werden, so dass die Compiler es nicht vermasseln, während die Optimierung zu tun.

Oder ist der Compiler berücksichtigt, dass die Zähler von der Mutex bewacht werden.

Ich verstehe, dass die Mutex ein Laufzeitmechanismus ist für die Synchronisation und „flüchtig“ Schlüsselwort ist eine Kompilierung Hinweis an den Compiler, das Richtige zu tun, während die Optimierungen zu tun.

Viele Grüße, -Jay.

War es hilfreich?

Lösung

Es gibt zwei grundsätzlich völlig andere Produkte hier, das ist immer verwirrt.

  • flüchtig
  • Gewinde, Schlösser, Speicher Barrieren usw.

volatile wird verwendet, um den zu sagen, Compiler Code zu erzeugen, die Variable aus dem Speicher zu lesen, nicht aus einem Register. Und um den Code nicht um neu anordnen. In der Regel nicht zu optimieren oder 'Short-Cuts' nehmen.

Speicher Barrieren (geliefert von Mutexe, Schlössern, etc.), wie von Herb Sutter in einer anderen Antwort zitiert, sind zu verhindern, dass die CPU von Neuordnungs Lese- / Schreibspeicheranforderungen, unabhängig davon, wie die Compiler sagte es zu tun. dh nicht optimieren, nehmen Sie nicht short cuts -. auf CPU-Ebene

ähnlich, aber in der Tat sehr verschiedene Dinge.

In Ihrem Fall, und in den meisten Fällen der Verriegelung, der Grund, dass flüchtige nicht notwendig ist, ist wegen der Funktion ruft zum Zweck der Verriegelung gemacht. dh:

Normale Funktion ruft Optimierungen betreffen:

external void library_func(); // from some external library

global int x;

int f()
{
   x = 2;
   library_func();
   return x; // x is reloaded because it may have changed
}

, wenn der Compiler prüfen library_func kann () und feststellen, dass es nicht x berührt, wird es wieder lesen x auf die Rückkehr. Dies ist auch OHNE volatil.

Threading:

int f(SomeObject & obj)
{
   int temp1;
   int temp2;
   int temp3;

   int temp1 = obj.x;

   lock(obj.mutex); // really should use RAII
      temp2 = obj.x;
      temp3 = obj.x;
   unlock(obj.mutex);

   return temp;
}

Nach dem Lesen obj.x für temp1, der Compiler wird wieder gelesen obj.x für temp2 - nicht wegen der Magie von Schlössern - sondern weil es nicht sicher, ob lock () geändert Obj. Sie könnten wahrscheinlich Compiler-Flags gesetzt, um aggressiv zu optimieren (No-Alias, etc.) und somit nicht x neu zu lesen, aber dann würde ein Haufen Code wahrscheinlich beginnt versagen.

Für temp3, der Compiler (hoffentlich) nicht wieder lesen obj.x. Wenn aus irgendeinem Grund obj.x zwischen TEMP2 und temp3 ändern könnte, dann würden Sie flüchtig verwenden (und Ihre Verriegelung würde / nutzlos gebrochen werden).

Schließlich, wenn Ihr Schloss () / unlock () Funktionen irgendwie inlined wurden, vielleicht könnte der Compiler den Code bewerten und sehen, dass obj.x nicht geändert hat bekommen. Aber ich garantiere, eines von zwei Dingen hier:   - der Inline-Code schließlich ruft einige OS-Ebene-Lock-Funktion (und verhindert so Auswertung) oder   -. Sie einige asm Speichersperr Anweisungen rufen, dass Ihr Compiler erkennen und somit Umsortierung vermeiden (dh, die in Inline-Funktionen wie __InterlockedCompareExchange gewickelt werden)

EDIT: P. S. Ich vergaß zu erwähnen - für pThreads Sachen sind einige Compiler als „POSIX-konform“ gekennzeichnet, das bedeutet unter anderem, dass sie die pthread_ Funktionen erkennen und nicht schlecht Optimierungen um sie herum. dh obwohl die C ++ Standardgewinde noch nicht erwähnt, diese Compiler tun (zumindest minimal).

So, kurze Antwort

Sie keine flüchtigen müssen.

Andere Tipps

Von Herb Sutter Artikel "Gebrauch Kritische Abschnitte (vorzugsweise Locks) zur Beseitigung des Races" ( http: // www .ddj.com / CPP / 201804238 ):

  

Also, für eine Umordnung Transformation gültig zu sein, muss er das Programm der kritischen Abschnitte respektieren, indem die eine Schlüsselregel kritische Abschnitte zu gehorchen: Code kann nicht aus einem kritischen Abschnitt bewegen. (Es ist immer in Ordnung für Code in bewegen.) Wir setzen diese goldene Regel durch die Forderung, symmetrische Einbahn Zaun Semantik für Anfang und Ende eines jeden kritischen Abschnitts, durch die Pfeile in Abbildung 1 dargestellt:

     
      
  • einen kritischen Abschnitt Eingabe ist eine acquire Operation oder ein impliziter acquire Zaun: Code kann nicht nach oben über den Zaun überqueren, das heißt, von einer ursprünglichen Position nach dem Zaun vor dem Zaun auszuführen bewegen. Code, der in der Quellcodereihenfolge vor dem Zaun erscheint, kann jedoch über den Zaun nach unten glücklich kreuzen später auszuführen.
  •   
  • einen kritischen Abschnitt verlassen ist eine Freigabeoperation oder eine implizite Freigabe Zaun: Dies ist nur die inverse Anforderung, dass Code kann den Zaun nach unten nicht kreuzen, nur nach oben. Es garantiert, dass alle anderen Thread, der die endgültige Version Schreib sieht, wird auch alle der schreibt, bevor es sehen.
  •   

Also für einen Compiler richtigen Code für eine Zielplattform zu erzeugen, wenn ein kritischer Abschnitt eingegeben und verlassen (und der Begriff kritischen Abschnitt wird verwendet in seiner allgemeinen Bedeutung, die nicht unbedingt in der Win32 Sinn von etwas durch eine CRITICAL_SECTION Struktur geschützt - der kritische Abschnitt durch andere Synchronisationsobjekte) die korrekten Erfassen und Freigeben Semantik geschützt werden muß befolgt werden. Sie sollten also nicht die gemeinsamen Variablen wie flüchtig haben zu markieren, solange sie nur in geschützten kritischen Abschnitte zugegriffen werden kann.

volatiles wird verwendet, um das Optimierungsprogramm zu informieren, immer den aktuellen Wert der Lage zu laden, anstatt sie in ein Register zu laden und davon ausgeht, dass es nicht ändern wird. Dies ist besonders wertvoll, wenn sie mit Dual-Port-Speicherplatz oder Standorte, die in Echtzeit von Quellen außerhalb des Thread aktualisiert werden können.

Der Mutex ist ein Laufzeit-Betriebssystem Mechanismus, der Compiler wirklich nichts weiß über - so der Optimierer, dass nicht berücksichtigen würde. Es wird mehr als ein Thread verhindern, dass die Zähler auf einmal zugreifen, aber die Werte dieser Zähler sind nach wie vor noch ändern, während der Mutex in Kraft ist.

So, du bist Markierung Vars flüchtig, weil sie von außen verändert werden können, und nicht, weil sie in einem Mutex Wache sind.

sie flüchtig halten.

Während diese auf der Threading-Bibliothek abhängen können Sie verwenden, mein Verständnis ist, dass jede anständige Bibliothek nicht erfordert die Verwendung von volatile.

In Pthreads, zum Beispiel , die Verwendung eines Mutex wird sichergestellt, dass Ihre Daten richtig im Gedächtnis wird.

EDIT: ich hiermit bestätigen Antwort tony als besser als meine eigenen.

Sie müssen noch das "flüchtig" Schlüsselwort.

Die mutexes verhindern, dass die Zähler aus dem gleichzeitigen Zugriff.

„flüchtig“ weist den Compiler tatsächlich um den Zähler zu verwenden anstatt sie in ein CPU-Register zwischenzuspeichern (die nicht wollten, durch den gleichzeitigen Thread aktualisiert werden).

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