Warum Synchronisation trotzdem wichtig ist
Das sind nur einige der möglichen Szenarien. Tatsächlich gibt es zahlreiche Varianten des obigen Designs - und keine zwei Implementierungen sind gleich. Zum Beispiel haben einige Designs einen O/F State. Einige verwenden Write-Back-Caches, andere Write-Through-Caches. Manche nutzen Snoop-Broadcasts, andere wiederum Snoop-Filter. Einige haben inklusive, andere exklusive Caches. Es gibt endlos viele Variationen - und Puffer haben wir noch nicht einmal erwähnt.
Das obige Beispiel nutzt zudem einen einfachen Prozessor mit nur zwei Cache-Ebenen. Das gleiche Protokoll kann aber auch rekursiv angewendet werden. Sie könnten leicht einen L3-Cache hinzufügen, der wiederum mehrere L2s koordiniert, wobei das gleiche Protokoll wie oben verwendet wird. Möglich wäre auch ein Multiprozessorsystem mit "Home Agents", die die Koordination über mehrere L3-Caches auf unterschiedlichen Chips übernehmen.
In jedem Szenario muss jeder Cache nur mit seinem Eltern-Cache (zum Abrufen von Daten/Berechtigungen) und seinen Kind-Caches (zum Gewähren/Widerrufen von Daten/Berechtigungen) kommunizieren. All das sollte so passieren, dass es für den Software-Thread unsichtbar ist. Aus Sicht der Software-Anwendung erscheint das Speicher-Subsystem als ein einziger, kohärenter Monolith ... mit sehr variablen Latenzen.
Warum dann überhaupt volatile Variablen?
Nachdem wir nun die unglaubliche Leistung und Kohärenz des Speichersystems Ihres Computers erörtert haben, fragen Sie sich vielleicht: Wenn Caches ohnehin synchronisiert werden - warum brauchen wir dann überhaupt volatile Variablen in Sprachen wie Java?
Das ist eine komplizierte Frage, die an anderer Stelle ausführlich beantwortet wird. Aber lassen Sie mich eine Sache dazu sagen: Werden Daten in CPU-Register eingelesen und gehalten, sind diese auch nicht synchron mit dem Cache oder Arbeitsspeicher.
Der Software-Compiler nimmt viele Optimierungen vor, wenn es darum geht, Daten in die Register zu laden, sie in den Cache zurückzuschreiben und sogar die Reihenfolge der Anweisungen zu ändern - und zwar alles unter der Annahme, dass der Code single threaded (also nicht nebenläufig) ausgeführt wird. Daher müssen alle Daten, die Race Conditions ausgesetzt sind, manuell durch nebenläufige Algorithmen und Sprachkonstrukte wie atomare und volatile Variablen geschützt werden.
Im Fall der volatilen Variablen in Java besteht ein Teil der Lösung darin, alle Lese-/Schreibvorgänge zu zwingen, die lokalen Register zu umgehen und stattdessen sofort Lese-/Schreibvorgänge im Cache auszulösen. Sobald die Daten in den L1-Cache gelesen/geschrieben werden, übernimmt das Hardware-Kohärenzprotokoll und bietet garantierte Kohärenz über alle globalen Threads hinweg.
So wird sichergestellt, dass, wenn mehrere Threads dieselbe Variable lesen oder in dieselbe Variable schreiben, alle synchron gehalten werden. So erreichen Sie eine Inter-Thread-Koordination in nur 1 Nanosekunde.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
- ohne Werbung
- mit ausgeschaltetem Javascript
- mit RSS-Volltext-Feed
Mögliche Abläufe für Schreib- und Lesevorgänge bei einer CPU mit vier Kernen |
Das ist so als ob Mann einen Trabi tunen will. Lieber eine bessere Programmiersprache...
Die Schwierigkeit ergibt sich daraus, dass es gerade in C eine große Menge "undefined...
Das kommt darauf an, auf welcher Position man auf der Erdscheibe wohnt und ob am Tag...