Ich komme hauptsächlich aus einem C ++ - Hintergrund, aber ich denke, diese Frage gilt für Threading in jeder Sprache. Hier ist das Szenario:
Wir haben zwei Threads (ThreadA und ThreadB) und einen Wert x im Shared Memory
Angenommen, der Zugriff auf x wird von einem Mutex (oder einem anderen geeigneten Synchronisationssteuerelement)
Wenn die Threads auf verschiedenen Prozessoren ausgeführt werden, was passiert, wenn ThreadA eine Schreiboperation ausführt, aber sein Prozessor das Ergebnis in seinen L2-Cache und nicht in den Hauptspeicher legt? Wenn ThreadB dann versucht, den Wert zu lesen, wird er nicht nur in seinem eigenen L1 / L2-Cache / Hauptspeicher suchen und dann mit dem alten Wert arbeiten, der dort vorhanden war?
Wenn das nicht der Fall ist, wie wird dieses Problem dann gehandhabt?
Wenn das der Fall ist, was kann dann dagegen getan werden?
Ihr Beispiel würde gut funktionieren.
Mehrere Prozessoren verwenden ein Kohärenzprotokoll wie MESI , um sicherzustellen, dass die Daten zwischen den Caches synchron bleiben. Bei MESI wird jede Cachezeile als entweder modifiziert, exklusiv gehalten, zwischen den CPUs geteilt oder ungültig angesehen. Wenn Sie eine Cache-Zeile schreiben, die von mehreren Prozessoren gemeinsam genutzt wird, wird sie in den anderen CPUs ungültig und hält die Caches synchron.
Das ist jedoch nicht genug. Unterschiedliche Prozessoren haben verschiedene Speichermodelle , und die meisten modernen Prozessoren unterstützen eine gewisse Stufe der Neuordnung der Speicherzugriffe . In diesen Fällen werden Speicherbarrieren benötigt.
Zum Beispiel, wenn Sie Thread A:
haben %Vor%Und Faden B:
%Vor%Da beide auf separaten Prozessoren ausgeführt werden, gibt es keine Garantie dafür, dass die in DoWork () ausgeführten Schreibvorgänge für Thread B sichtbar sind, bevor das Schreiben in workDone und DoSomethingWithResults () mit einem möglicherweise inkonsistenten Zustand fortgesetzt wird. Speicherbarrieren garantieren eine gewisse Reihenfolge der Lese- und Schreibvorgänge - das Hinzufügen einer Speicherbarriere nach DoWork () in Thread A würde alle von DoWork ausgeführten Lese- / Schreibvorgänge vor dem Schreiben in workDone erzwingen, sodass Thread B eine konsistente Ansicht erhalten würde. Mutexes stellen inhärent eine Speicherbarriere bereit, so dass Lese- / Schreibvorgänge keinen Aufruf zum Sperren und Entsperren weitergeben können.
In Ihrem Fall würde ein Prozessor den anderen signalisieren, dass er eine Cache-Zeile verschmutzt hat und die anderen Prozessoren aus dem Speicher neu laden muss. Wenn Sie den Mutex zum Lesen und Schreiben des Werts anfordern, wird sichergestellt, dass die Änderung des Speichers für den anderen Prozessor in der erwarteten Reihenfolge sichtbar ist.
Die meisten Sperr-Primitive wie Mutexe implizieren Speicherbarrieren . Diese erzwingen einen Cache-Flush und laden neu.
Zum Beispiel
%Vor%Im Allgemeinen versteht der Compiler Shared Memory und unternimmt beträchtliche Anstrengungen, um sicherzustellen, dass der Shared Memory an einem gemeinsam nutzbaren Ort platziert wird. Moderne Compiler sind sehr kompliziert in der Art, wie sie Operationen und Speicherzugriffe bestellen; Sie neigen dazu, die Art von Threading und Shared Memory zu verstehen. Das soll nicht heißen, dass sie perfekt sind, aber im Allgemeinen wird ein Großteil des Interesses vom Compiler erledigt.
C # hat einige Unterstützung für diese Art von Problemen.
Sie können eine Variable mit dem Schlüsselwort volatile
markieren, wodurch sie auf allen CPUs synchronisiert wird.
Der andere Teil ist ein syntaktischer Wrapper um die .NET-Methoden Threading.Monitor.Enter (x) und Threading.Monitor.Exit (x), wobei x eine zu sperrende Variable ist. Dies führt dazu, dass andere Threads, die versuchen, x zu sperren, warten müssen, bis der gesperrte Thread Exit (x) aufruft.
%Vor%Tags und Links multithreading caching multiprocessor