Frage

In der Programmiersprache C und Pthreads als Threading-Bibliothek; Sie Variablen / Strukturen, die zwischen Threads gemeinsam genutzt werden müssen werden als flüchtig erklärt? Unter der Annahme, dass sie durch eine Sperre geschützt werden könnten oder nicht (Barrieren vielleicht).

Ist die pthread POSIX-Standard jeder haben sagen über dieses, ist dieser Compiler-abhängige oder keines von beiden?

Bearbeiten, um hinzuzufügen: Danke für die tollen Antworten. Aber was, wenn Sie nicht mit Schlössern; was ist, wenn Sie Barrieren verwenden zum Beispiel? Oder Code, der Primitiven wie Vergleichs- und Swap direkt und atomar eine gemeinsame Variable. ..

War es hilfreich?

Lösung

Ich denke, eine sehr wichtige Eigenschaft flüchtig ist, dass es die Variable in den Speicher geschrieben werden, macht, wenn geändert, und aus dem Speicher jedes Mal auf sie zugegriffen wieder gelesen. Die anderen Antworten hier flüchtig und Synchronisation mischten, und es ist klar, von einigen anderen Antworten als dies, dass flüchtig ist kein sync primitiver (Kredit, in dem Kredit passend ist).

Aber es sei denn, Sie flüchtig verwenden, der Compiler ist frei, um die gemeinsam genutzten Daten in einem Register für längere Zeit zwischenzuspeichern ... wenn Sie Ihre Daten wollen tatsächliche Speicher vorhersagbar geschrieben werden geschrieben werden und nicht nur in einem Cache gespeicherten registrieren vom Compiler nach eigenem Ermessen, müssen Sie es als flüchtig markieren. Alternativ, wenn Sie nur die freigegebenen Daten zugreifen, nachdem Sie eine Funktion verlassen haben, zu modifizieren es, könnten Sie in Ordnung sein. Aber ich würde vorschlagen, nicht auf blindes Glück verlassen, um sicherzustellen, dass die Werte zurückgeschrieben werden aus den Registern in dem Speicher.

Vor allem auf Register reiche Maschinen (das heißt, nicht x86), Variablen für eine recht lange Zeit in den Registern leben können, und ein guter Compiler auch Teile von Strukturen oder ganzer Strukturen in Registern zwischengespeichert werden kann. So sollten Sie flüchtig verwenden, aber für die Leistung, kopieren Sie auch Werte für lokale Variablen für die Berechnung und führen Sie dann eine explizite Schreib zurück. Im Wesentlichen effizient volatilen unter Verwendung von Mitteln in Ihrem C-Code ein bisschen Load-Store-Denkens zu tun.

In jedem Fall müssen Sie positiv irgendeine Art von OS-Level Sync Mechanismus verwenden, um ein korrektes Programm zu erstellen.

Ein Beispiel für die Schwäche von flüchtigen, siehe meinen Deckers Algorithmus Beispiel unter http: //jakob.engbloms .se / Archiv / 65 , was ziemlich gut belegt, dass volatile nicht synchronisieren funktioniert.

Andere Tipps

Solange Sie Schlösser verwenden Zugriff auf die Variable zu steuern, müssen Sie auf sie nicht flüchtig. In der Tat sind, wenn Sie flüchtig auf jede Variable setzen sind Sie wahrscheinlich schon falsch.

https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded- Programmierung /

Die Antwort ist absolut eindeutig, NO. Sie brauchen nicht zusätzlich zu richtigen Synchronisation Primitiven ‚volatile‘ zu verwenden. Alles, was von diesen Primitiven getan getan werden muss.

Die Verwendung von ‚flüchtigen‘ ist weder notwendig noch ausreichend. Es ist nicht notwendig, da die richtige Synchronisation Primitiven ausreichend ist. Es ist nicht ausreichend, da es nur einige Optimierungen deaktiviert, alle von denen nicht, die Sie beißen könnten. Zum Beispiel garantiert sie weder die Unteilbarkeit oder die Sicht auf eine andere CPU.

  

Aber es sei denn, Sie flüchtig verwenden, der Compiler ist frei, um die gemeinsam genutzten Daten in einem Register für längere Zeit zwischenzuspeichern ... wenn Sie Ihre Daten wollen tatsächliche Speicher vorhersagbar geschrieben werden geschrieben werden und nicht nur in einem Cache gespeicherten registrieren vom Compiler nach eigenem Ermessen, müssen Sie es als flüchtig markieren. Alternativ, wenn Sie nur die freigegebenen Daten zugreifen, nachdem Sie eine Funktion verlassen haben, zu modifizieren es, könnten Sie in Ordnung sein. Aber ich würde vorschlagen, nicht auf blindes Glück verlassen, um sicherzustellen, dass die Werte zurückgeschrieben werden aus den Registern in dem Speicher.

Richtig, aber auch wenn Sie flüchtig verwenden, die CPU ist frei, um die gemeinsam genutzten Daten in einem Schreibbuchungspuffer für längere Zeit zwischenzuspeichern. Der Satz von Optimierungen, die Sie beißen können, ist nicht genau das gleiche wie die von Optimierungen, dass ‚flüchtig‘ deaktiviert. Also, wenn Sie 'volatile' verwenden, können Sie sind auf blindem Glück verlassen.

Auf der anderen Seite, wenn Sie Synchronisations Primitiven mit definierten Multi-Threaded-Semantik verwenden, werden Sie garantiert, dass die Dinge funktionieren. Als Plus, nehmen Sie nicht den enormen Performance-Hit ‚volatile‘. Also, warum die Dinge nicht tun, die Art und Weise?

Es ist eine weit verbreitete Vorstellung, dass das Schlüsselwort gut flüchtig ist für Multi-Thread-Programmierung.

Hans Boehm weist darauf hin, dass es nur drei portable Anwendungen für volatile:

  • volatile verwendet werden können lokale Variablen im gleichen Umfang als setjmp, deren Wert markieren sollte über eine longjmp erhalten bleiben. Es ist unklar, welcher Anteil solcher Anwendungen verlangsamt werden würde, da die Unteilbarkeit und Bestell Einschränkungen keine Auswirkungen haben, wenn es keine Möglichkeit gibt, die lokale Variable in Frage zu teilen. (Es ist noch unklar, welcher Anteil solcher Anwendungen durch die Forderung, alle Variablen über einen longjmp bewahrt werden würde verlangsamt, aber das ist eine andere Angelegenheit und wird hier nicht berücksichtigt.)
  • flüchtige verwendet werden kann, wenn Variablen „extern modifiziert“ werden kann, aber die Änderung in der Tat wird synchron durch den Faden selbst ausgelöst, z.B. da die zugrunde liegenden Speicher an mehreren Stellen abgebildet wird.
  • A flüchtige sigatomic_t verwendet werden kann, mit einem Signal-Handler in dem gleichen Thread, in einer eingeschränkten Weise zu kommunizieren. Man könnte in Erwägung ziehen, die Anforderungen an die sigatomic_t Fall zu schwächen, aber das scheint eher eingängig.

Wenn Sie Multi-Threading aus Gründen der Geschwindigkeit, Code Verlangsamung ist definitiv nicht das, was Sie wollen. Für Multi-Thread-Programmierung gibt es zwei wichtige Fragen, die volatilen werden oft fälschlicherweise angenommen Adresse:

  • Unteilbarkeit
  • Speicher Konsistenz , das heißt die Reihenfolge eines Threads Operationen wie von einem anderen Thread gesehen.

Lassen Sie uns mit (1) zuerst beschäftigen. Volatile bietet keine Garantie für Atom liest oder schreibt. Zum Beispiel kann ein flüchtiger Lese- oder Schreib einer 129-Bit-Struktur ist auf den meisten modernen Hardware nicht atomar sein würde. Ein flüchtiges Lesen oder Schreiben eines 32-Bit-int ist atomar auf den meisten modernen Hardware, aber volatile hat nichts damit zu tun . Es wäre wahrscheinlich ohne die volatilen atomar sein. Die Unteilbarkeit ist an der Laune des Compilers. Es gibt nichts in der C oder C ++ Standards, die sagen, es atomar sein muss.

Jetzt prüfen Ausgabe (2). Manchmal denken Programmierer flüchtiger als Optimierung flüchtiger Zugriffe ausschalten. Das ist weitgehend auch in der Praxis. Aber das ist nur die flüchtigen Zugriffe, nicht die nichtflüchtigen diejenigen. Betrachten Sie dieses Fragment:

 volatile int Ready;       

    int Message[100];      

    void foo( int i ) {      

        Message[i/10] = 42;      

        Ready = 1;      

    }

Es wird versucht, etwas sehr vernünftig in Multi-Thread-Programmierung zu tun: eine Nachricht schreiben und es dann zu einem anderen Thread senden. Der andere Thread wartet, bis Bereit wird nicht Null und dann Nachricht lesen. Versuchen Sie, diese Zusammenstellung mit "gcc -O2 -S" mit gcc 4.0 oder icc. Beide werden in den Laden Bereit zuerst tun, so kann es bei der Berechnung von i / 10 überlappt werden. Die Nachbestellung ist kein Compiler-Fehler. Es ist ein aggressiver Optimierer seine Arbeit zu tun.

Man könnte denken, die Lösung ist alle Referenz Ihres Gedächtnisses flüchtig zu markieren. Das ist einfach nur dumm. Wie die früheren Zitate sagen, es wird nur der Code verlangsamen. Schlimmste noch, ist es vielleicht nicht das Problem beheben. Auch wenn die Compiler die Verweise nicht neu anordnen, könnte die Hardware. In diesem Beispiel wird x86-Hardware neu ordnet es nicht. Weder wird eine Itanium (TM) Prozessor, weil Itanium Compilern Speicher Zäunen für flüchtige speichert einzufügen. Das ist eine kluge Itanium-Erweiterung. Aber Chips wie Power (TM) wird neu anordnen. Was Sie wirklich brauchen für die Bestellung sind Speicher Zäune , die auch als Speicher Barrieren . Ein Speicherzaun verhindert Neuordnen von Speicheroperationen über den Zaun, oder in einigen Fällen verhindert Umordnung in einer direction.Volatile nichts mit Speichern Zäunen zu tun hat.

Was ist also die Lösung für Multi-Threaded-Programmierung? Verwenden Sie eine Bibliothek oder Spracherweiterung, die die atomare und Zaun Semantik implementiert. Bei bestimmungsgemäßer Verwendung werden die Operationen in der Bibliothek, die richtigen Zäune einfügen. Einige Beispiele:

  • POSIX-Threads
  • Fenster (TM) Themen
  • OpenMP
  • TBB

Basierend auf Artikel von Arch Robison (Intel)

Nach meiner Erfahrung, nein; Sie müssen nur richtig, sich Mutex, wenn Sie auf diese Werte, oder strukturieren Sie Ihr Programm so zu schreiben, dass die Fäden stoppen, bevor sie Daten zugreifen müssen, die auf einem anderen Thread Aktionen abhängt. Mein Projekt, x264, benutzt diese Methode; Fäden eine enorme Menge an Daten gemeinsam nutzen, aber die überwiegende Mehrheit davon nicht mutexes müssen, weil sein entweder schreibgeschützt oder ein Thread wird die Daten wartet verfügbar und fertig gestellt werden, bevor sie es zugreifen muss.

Nun, wenn Sie viele Threads haben, die in ihren Betrieben alle stark verschachtelt sind (sie hängen an jedem Ausgang andere auf einem sehr feinkörnig Ebene), kann dies viel schwieriger sein - in der Tat, in einem solchen Fall ich halte würde das Threading-Modell erneuten Besuch, um zu sehen, ob es vielleicht sauberer mit mehr Trennung zwischen Threads getan werden kann.

NO.

Volatile ist nur erforderlich, wenn Sie einen Speicherplatz zu lesen, die unabhängig von den CPU-Lese- / Schreibbefehle ändern können. In der Situation von Threading ist die CPU in der vollen Kontrolle über Lese- / schreibt für jeden Thread in dem Speicher, daher kann der Compiler den Speicher übernimmt kohärent und optimiert die CPU-Befehle unnötigen Speicherzugriff zu reduzieren.

Die primäre Verwendung für volatile ist für Memory-Mapped I / O-Zugriff. In diesem Fall kann die darunter liegende Vorrichtung den Wert einer Speicherstelle unabhängig von der CPU ändern. Wenn Sie nicht volatile unter dieser Bedingung verwenden, die CPU einen zuvor im Cache-Speicher-Wert verwenden, anstatt den neu aktualisierten Wert zu lesen.

Flüchtige nur nützlich wäre, wenn Sie benötigen absolut keine Verzögerung zwischen wenn ein Thread schreibt etwas und ein anderer Thread liest es. Ohne irgendeine Art von Sperre, Sie aber haben keine Ahnung von , wenn der anderen Thread der Daten geschrieben hat, nur, dass es der letzte mögliche Wert ist.

Für einfache Werte (int und float in ihren verschiedenen Größen) ein Mutex vielleicht zu viel des Guten, wenn Sie keine explizite Synch-Punkt benötigen. Wenn Sie nicht über einen Mutex oder Sperre von einer Art verwenden, sollten Sie die Variable volatile deklarieren. Wenn Sie einen Mutex verwenden Sie sind alle gesetzt.

Für komplizierte Typen, müssen Sie einen Mutex verwenden. Operationen auf sie sind nicht-atomar, so dass Sie eine halbe geänderte Version ohne Mutex lesen konnten.

Flüchtige Mittel, die wir in den Speicher gehen zu erhalten oder diesen Wert gesetzt. Wenn Sie nicht flüchtig gesetzt, kann der kompilierte Code speichern die Daten in einem Register für eine lange Zeit.

Was dies bedeutet, ist, dass Sie Variablen markieren sollten, die Sie zwischen den Threads als flüchtig teilen, so dass Sie Situationen nicht haben, wo ein Thread den Wert beginnt zu modifizieren, aber nicht schreibt das Ergebnis, bevor ein zweites Gewinde entlang kommt und versucht, lesen Sie den Wert.

Flüchtige ist ein Compiler Hinweis, dass bestimmte Optimierungen deaktiviert. Die Ausgabebaugruppe des Compilers können, ohne es sicher gewesen, aber Sie sollten es immer für gemeinsame Werte verwenden.

Dies ist besonders wichtig, wenn Sie die teuere Thread Sync-Objekte von Ihrem System zur Verfügung gestellt nicht verwenden - Sie könnten zum Beispiel eine Datenstruktur, wo Sie es gültig mit einer Reihe von Atom Änderungen halten. Viele Stapel, die zuteilen nicht Speicher sind Beispiele für solche Datenstrukturen, weil Sie einen Wert auf den Stapel hinzufügen können dann das Ende Zeiger bewegen oder einen Wert aus dem Stapel entfernen, nachdem das Ende Zeiger zu bewegen. Wenn eine solche Struktur der Umsetzung, volatile entscheidend wird, um sicherzustellen, dass Ihre atomarer Befehle tatsächlich atomar sind.

Der eigentliche Grund ist, dass die C-Sprache semantisch auf eine Single-Threaded-abstrakte Maschine basiert . Und der Compiler ist in seinem eigenen Recht, das Programm so lange, wie das Programm der ‚beobachtbare Verhalten‘ auf der abstrakten Maschine zu verwandeln unverändert bleiben. Es können benachbarte oder überlappende fusionieren Speicherzugriffe, Redo einen Speicherzugriff mehrere Male (auf Register zum Beispiel verschütten) oder einfach einen Speicherzugriff verwerfen, wenn sie das Programm der Verhaltensweisen denkt, wenn es ausgeführt wird in ein einzelner Thread , ändert sich nicht. Deshalb, wie Sie vermuten können, das Verhalten ändern, wenn das Programm soll eigentlich in einer Multi-Threaded-Art und Weise zu ausführen.

Wie Paul Mckenney wies darauf hin, in einem berühmten Linux-Kernel-Dokument :

  

Es _must_not_ davon ausgegangen werden, dass der Compiler tun, was Sie wollen        mit Speicherreferenzen, die nicht durch READ_ONCE geschützt ist () und        WRITE_ONCE (). Ohne sie ist der Compiler in seine Rechte        tun alle Arten von „kreativen“ Transformationen, die in abgedeckt sind        Der Compiler BARRIER Abschnitt.

READ_ONCE () und WRITE_ONCE () werden als flüchtige Abgüsse auf referenzierten Variablen definiert. Also:

int y;
int x = READ_ONCE(y);

entspricht:

int y;
int x = *(volatile int *)&y;

Also, wenn Sie einen ‚flüchtigen‘ Zugang zu machen, werden Sie nicht sicher sein, dass der Zugriff geschieht genau einmal , egal welche Synchronisationsmechanismus Sie verwenden. eine externe Funktion (pthread_mutex_lock zum Beispiel) aufrufen kann, den Compiler zwingen Speicher keine Zugriffe auf globale Variablen. Dies geschieht jedoch nur, wenn der Compiler, um herauszufinden, schlägt fehl, ob die externe Funktion diese globalen Variablen verändert oder nicht. Moderne Compiler anspruchsvolle inter-Verfahren Analyse und Linkzeit-Optimierung beschäftigt machen diesen Trick einfach nutzlos.

Insgesamt sollten Sie Variablen geteilt durch mehrere Threads markieren flüchtig oder sie flüchtige Casts mit zuzugreifen.


Wie Paul McKenney hat auch darauf hingewiesen:

  

Ich habe das Glitzern in ihren Augen zu sehen, wenn sie Optimierungstechniken zu diskutieren, dass Sie nicht Ihre Kinder darüber wissen!

wollen würden,

Aber sehen, was passiert mit C11 / C ++ 11 .

Ich bekomme es nicht. Wie funktioniert die Synchronisierung Primitiven die Compiler zwingen, den Wert einer Variablen neu zu laden? Warum sollte es nicht benutzen Sie einfach die neueste Version hat es schon?

Volatile bedeutet, dass die Variable außerhalb des Bereichs des Codes aktualisiert wird, und somit kann der Compiler nicht annehmen, dass es den aktuellen Wert der es wissen. Auch sind Speicherbarrieren nutzlos, da der Compiler, der den Speicher Barrieren nicht bewusst ist (oder?), Vielleicht noch einen zwischengespeicherten Wert verwendet werden.

Einige Leute offensichtlich davon aus, dass der Compiler die Synchronisation Anrufe als Speicher Barrieren behandelt. "Casey" wird vorausgesetzt, es gibt genau eine CPU.

Wenn die Sync-Primitive sind externe Funktionen und die Symbole in Frage sind sichtbar außerhalb der Übersetzungseinheit (globale Namen exportiert Zeiger, exportierte Funktion, die sie ändern können), dann der Compiler sie behandeln - oder jede andere externe Funktionsaufruf - -. als Speicher Zaun in Bezug auf alle äußerlich sichtbaren Objekte

Ansonsten sind Sie auf eigene Faust. Und flüchtige kann das beste Werkzeug für die Herstellung der Compiler verfügbar sein produzieren richtig, schnellen Code. Es wird in der Regel nicht tragbar sein, obwohl, wenn Sie flüchtig brauchen und was es tatsächlich für Sie tut hängt viel von dem System und Compiler.

Nein.

Als erstes ist volatile nicht erforderlich. Es gibt zahlreiche andere Operationen, die garantierte multithreaded Semantik bereitzustellen, die volatile nicht verwenden. Dazu gehört atomare Operationen, Mutexe, und so weiter.

Zweitens ist volatile nicht ausreichend. Der C-Standard bietet keine Garantien über Multithreading-Verhalten für Variablen deklariert volatile.

So weder notwendig noch ausreichend, da sich mit der es nicht viel Sinn ist.

Eine Ausnahme würde bestimmte Plattformen (wie zB Visual Studio), wo es multithreaded Semantik dokumentiert hat.

Variablen, die zwischen Threads gemeinsam genutzt werden sollte ‚flüchtig‘ deklariert werden. Dies teilt die dass Compiler, wenn ein Faden auf eine solche Variablen schreibt, sollte die Schreibspeicher sein, um (Im Gegensatz zu einem Register entgegengesetzt).

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