Ich habe also einen booleschen Typ in C ++ auf einem Multiprozessor-Rechner. Die Variable beginnt das Leben als wahr, und dann gibt es ein paar Threads, von denen einer oder mehrere es falsch schreiben könnten.
Gleichzeitig können diese Threads diese Variable auch lesen, um ihren Status zu überprüfen. Es ist mir egal, ob das Lesen dieser Variablen mit einem der Schreibvorgänge synchronisiert ist, sie finden jeweils an verschiedenen Stellen im Code statt, und es spielt keine Rolle, ob sie vor oder nach einem bestimmten Schreibvorgang kommt. Brauche ich jetzt eine Sperre für diesen booleschen Wert?
Der einzige Weg, auf dem ich eine Sperre benötigen würde, ist, wenn der Speicher auf sehr niedriger Ebene durch zwei konkurrierende Schreibvorgänge beschädigt werden kann. Wenn zum Beispiel eine Assembler-Anweisung auf dem Prozessor A 0 in das Byte schreibt, das den Booleschen Wert zur gleichen Zeit darstellt, wie der Prozessor B dasselbe tut ... und anstatt 0 geschrieben zu haben, endet der Speicher mit dem Wert 22 oder etwas. Das könnte etwas durcheinander bringen.
Also, im Allgemeinen, wenn Proc A 3 an einen Speicherort schreibt, während Proc B 7 ohne Synchronisation schreibt, bin ich garantiert am Ende mit mindestens 3 oder 7? Oder ist es so einfach, die Erinnerung zu brechen?
Bearbeiten:
Danke für die Kommentare Jungs. Noch ein paar Infos: Es gibt natürlich Synchronisation im Programm. Zusammenfassend sagt das fragliche Flag, ob ein bestimmter Speicherpool "schmutzig" ist (muss komprimiert werden). Jeder Thread kann daher entscheiden, dieses Flag auf false zu setzen (was bedeutet, dass der Pool schmutzig ist). Wenn Sie beispielsweise Speicher aus dem Pool freigeben, wird es schmutzig. Jeder Thread kann dann auch dieses Flag lesen und ein anderes Flag setzen, um anzuzeigen, dass eine Bereinigung erforderlich ist - diese Überprüfung wird durchgeführt, wenn Speicher aus dem Pool zugewiesen wird, die Bereinigung wird signalisiert, wenn der Speicher knapp ist. Irgendwo in meinem kritischen Hauptabschnitt zwischen den Iterationen, wo jeder Thread nach mehr zu verarbeitenden Daten sucht, lasse ich die Threads dieses zweite Flag überprüfen und tue etwas Passendes, um sicherzustellen, dass: alle anderen Theads ihre aktuelle Iteration beenden, ein Thread räumt den Speicher auf, setzt das erste Flag zurück auf "true" (wie im Pool ist nicht schmutzig), setzt das zweite Flag auf "false" zurück und gibt dann alle Threads erneut frei.
Ich glaube also nicht, dass ich eine Sperre brauche, denn: Eine Sperre würde sicherstellen, dass ein Schreibvorgang nicht gleichzeitig mit einem anderen Schreibvorgang oder einem Lesevorgang stattfindet. Aber wen interessiert das schon, solange die Hardware mir nicht schadet, ist das Worst-Case-Szenario, dass das Lesen zufällig vor oder nach dem Schreiben passiert - das ist dasselbe, was passieren würde, wenn ich es mit einem Schloss schützen würde, Gerade dann würden wir wirklich sicher sein, dass es vorher oder nachher kam ...
Ich denke, das gleiche Argument gilt für die zweite Flagge, die ich oben erwähnt habe.
Wenn Sie nur den Status der Variablen überprüfen und sie in einer Richtung auf "false" setzen, gibt es nicht viel zu bedenken, außer dass einige der Threads etwas zu spät kommen, um zu sehen, dass die Variable bereits auf false gesetzt ist . (Dies kann bis zu einem gewissen Grad durch die Verwendung eines 'volatile' Schlüsselwortes überwunden werden.) Dann können zwei Threads es auf false setzen, was kein Problem ist, da die Variable auf einen einzigen Wert in einer Richtung eingestellt ist. Angenommen, boolescher Schreibzugriff auf den Speicherort ist nicht garantiert atomar. Was ist der Schaden? Der endgültige Wert, den beide schreiben werden, ist derselbe.
Trotzdem müssten Sie eine Methode zum Sperren verwenden, wenn:
Auf den meisten Standard-Hardware-Systemen sind Lese- und Schreibvorgänge mit einem Wort atomar, sodass zwei (oder mehr) konkurrierende Schreibvorgänge (und Lesevorgänge) an demselben Speicherort den Wert nicht beschädigen. Was hier wichtig ist, ist Cachekohärenz zwischen CPUs.
Wiederum könnte man auf Commodity-Hardware einfach die einzige boolesche Variable volatile
markieren (die für die gleichzeitige Programmierung als nutzlos erklärt wurde), um zu verhindern, dass der Compiler sie in ein Register optimiert, aber nur wenn Sie kümmern sich wirklich nicht um die Reihenfolge der Schreibvorgänge.
Lassen Sie mich dies mit einer Checkliste wiederholen:
Wenn Sie drei starke "Ja" -Antworten haben, können Sie davon ausgehen, diese Flagge nicht zu schützen. Ziehen Sie immer noch in Erwägung, vor dem Lesen der Variablen die Speicherbarriere einzufügen und die Speicherbarriere zu entfernen, bevor Sie sie schreiben. Mein Vorschlag wäre jedoch, das Design zu überdenken und klare synchrone Inter-Thread-Kommunikation und Event-Sequenzierung auszulegen.
Hoffe, das hilft.
Es ist ein einfacher Weg, Dinge zu brechen. Mit einem booleschen Wert können Sie die meiste Zeit in Ordnung sein, es werden jedoch keine Garantien gegeben.
Sie haben zwei Möglichkeiten: Verwenden Sie einen Mutex (lock) oder verwenden Sie atomare Primitive. Atom-Primitive verwenden Hardware-Befehle, um den Test durchzuführen und Operationen auf eine Thread-sichere Weise einzustellen, ohne einen tatsächlichen Mutex zu erfordern, und sind eine leichtere Lösung. Der GNU-Compiler bietet Zugriff auf atomare Operationen über architekturspezifische Erweiterungen. Es gibt auch portable atomare Operationsbibliotheken, die herumschweben; Die Glib C-Bibliothek stellt atomare Operationen bereit, die auf die Verwendung eines Mutex zurückgreifen, wenn atomare Primitive nicht verfügbar sind, obwohl es eine ziemlich schwere Bibliothek mit vielen ist andere Funktionen auch.
Es gibt die Boost.Atomic -Bibliothek, die atomare Operationen für C ++ abstrahiert; basierend auf seinem Namen sieht es so aus, als ob es in die Boost C ++ - Bibliotheksammlung aufgenommen werden soll, hat es aber noch nicht geschafft.
Für einen Bool ist die Antwort im Allgemeinen nein, ein Mutex wird nicht benötigt, aber (wie Michael E bemerkte) kann alles passieren, also müssen Sie wahrscheinlich mehr über Ihren Bogen wissen, bevor Sie eine solche Entscheidung treffen. Noch ein Hinweis: Der Code benötigt möglicherweise immer noch eine Sperre um die Gesamtlogik, die dem Bool zugeordnet ist, insbesondere wenn der Bool im Verlauf der Logik einer Routine mehr als einmal gelesen wird.
Einige tolle Blogs, die ich gelesen habe, um mich auf meinen Multithread-Zehen zu halten:
Beste Grüße,
Sie fragen nach zwei Dingen.
Zuerst fragen Sie nach der Atomizität der Bool-Zuweisung. Nein, es gibt keine Garantie, dass die boolesche Zuweisung ein atomarer Vorgang ist. In der Praxis ist es normalerweise so, aber Sie sollten sich nicht darauf verlassen. Einige seltsame Architektur kann Bool-Zuweisung in vielen Maschinenanweisungen implementieren ...
Secondary Sie fragen nach Datenverfälschung aufgrund von parallelen Schreibvorgängen - in der Praxis erfolgt der Transport von der CPU zum Speicher über einen Bus, der fast immer mehr Bits enthält als primitive Typen, an denen Sie gerade arbeiten. Eine solche Korruption kann also bei sehr seltsamen Architekturen auftreten oder bei der Arbeit an großen Zahlen (wird vom System nicht nativ unterstützt). In der Praxis werden Sie normalerweise 3 oder 7 bekommen. Aber wieder können Sie sich nicht darauf verlassen.
Um das zu beenden - Sie brauchen ein Schloss.
Auf den meisten Standardhardware sind Lese- und Schreibvorgänge mit einem Wort keine atomaren Operationen; Denken Sie daran, dass Sie hier virtuelle Arbeitsspeichercomputer haben und eine dieser Operationen möglicherweise einen Seitenfehler verursacht.
Allgemein gesagt werden Sie es wahrscheinlich einfacher und schneller finden, Mutexe als Routine zu verwenden, als sich den Kopf zu kratzen und sich zu fragen, ob Sie dieses Mal ohne eins davonkommen werden. Hinzufügen eines, wo es nicht notwendig ist, verursacht keinen Fehler; lass einen da raus, wo es ist.
Tags und Links c++ synchronization