Angenommen, wir haben eine gemeinsam genutzte Warteschlange (implementiert mit einem Array), auf die zwei Threads zugreifen können, eine zum Lesen von Daten und eine andere zum Schreiben von Daten. Jetzt habe ich ein Problem der Synchronisation. Ich implementiere dies mit Win32 API (EnterCriticalSection etc.).
Aber meine Neugier ist, was wird der kritische Abschnitt Code in Enqueue-und Dequeue-Operationen der Warteschlange?
Nur weil zwei Threads eine gemeinsame Ressource verwenden? Warum ich kein Problem sehe, ist dies: Vorder- und Rückseite werden beibehalten, also wenn ReaderThread liest, kann es vom Frontend lesen und wenn WriterThread schreibt, kann es leicht in das hintere Ende schreiben.
Welche möglichen Probleme können auftreten?
Bei einer Implementierung von Warteschlangen für einen einzelnen Producer / Consumer sind Sperren nicht erforderlich. Legen Sie einfach eine Bedingung fest, bei der der Produzent nicht in die Warteschlange schreiben kann, wenn die Warteschlange voll ist und der Benutzer die Warteschlange nicht lesen kann, wenn sie leer ist. Außerdem schreibt der Producer immer in einen Zeiger tail
, der auf den ersten verfügbaren leeren Slot in der Warteschlange verweist, und der Consumer liest von einem head
-Zeiger, der den ersten ungelesenen Slot in der Warteschlange darstellt.
Ihr Code kann wie das folgende Codebeispiel aussehen (Hinweis: Ich nehme in einer initialisierten Warteschlange an, dass tail == head
ist, und dass beide Pointer deklariert werden volatile
, so dass ein optimierender Compiler die Sequenz nicht neu anordnet Operationen innerhalb eines bestimmten Threads: Auf x86 sind aufgrund des starken Speicherkonsistenzmodells für die Architektur keine Speicherbarrieren erforderlich, aber dies würde sich bei anderen Architekturen mit schwächeren Speicherkonsistenzmodellen ändern, bei denen Speicherbarrieren erforderlich wären):
Die Funktion next_slot
inkrementiert einfach den Zeiger head
oder tail
, so dass sie einen Zeiger auf den nächsten Slot im Array zurückgibt und alle Array-Wrap-Around-Funktionen berücksichtigt.
Schließlich garantieren wir die Synchronisation im Einzelproduzenten- / Konsumentenmodell, da wir den tail
-Pointer nicht inkrementieren, bis er die Daten in den Slot geschrieben hat, auf den er gezeigt hat, und wir den head
-Pointer nicht bis inkrementieren Wir haben die Daten von dem Slot gelesen, auf den sie zeigen. Daher wird ein Aufruf von dequeue
erst dann gültig zurückgegeben, wenn mindestens ein Aufruf von enequeue
erfolgt ist, und der Zeiger tail
überschreibt niemals den Zeiger head
wegen der Überprüfung in enqueue
. Darüber hinaus inkrementiert nur ein Thread den Zeiger tail
, und ein Thread erhöht den Zeiger head
, so dass keine Probleme mit einem gemeinsamen Lesen oder Schreiben von oder zu demselben Zeiger auftreten, was zu Synchronisationsproblemen führen würde, die eine Sperre oder erfordern eine Art atomare Operation.
Tags und Links c++ multithreading critical-section