Angenommen, es gibt eine Klasse, die ein öffentliches Int-Zählerfeld enthält, auf das mehrere Threads zugreifen können, und diese Zahl steigt oder verringert sich nur.
Beim Hinzufügen dieses Feldes, welche der folgenden Schemata sollten verwendet werden und warum?
- lock(this.locker) this.counter++;
- Interlocked.Increment (ref this.counter);
- Ändere den Zugriffsmodifikator von Counter auf öffentlich volatil
Am schlimmsten (keine davon funktioniert wirklich)
Ändere den Zugriffsmodifikator von Counter auf öffentlich volatil
Dieser Ansatz ist tatsächlich überhaupt nicht sicher, und der Punkt bei volatile ist, dass mehrere Threads, die auf mehreren CPUs laufen, Daten puffern und ausgeführte Anweisungen neu anordnen.
Wenn es nicht flüchtig ist, muss CPU B nach einer Erhöhung von CPU A um einen Wert eine Weile warten, um den erhöhten Wert zu sehen, was zu Problemen führen kann.
Wenn es flüchtig ist, sorgt das dafür, dass beide CPUs gleichzeitig denselben Wert sehen. Aber es verhindert nicht, dass Lese- und Schreiboperationen querschneiden.
Um einer Variablen Mehrwert hinzuzufügen, braucht man tatsächlich drei Schritte
1. Lesen, 2. Fügen Sie hinzu 3. Schreiben Sie
Angenommen, Thread A liest den Wert von Zähler als 1 und ist nicht bereit zu steigen, dann liest Thread B auch den Wert von Zähler als 1, und dann beginnen beide Threads, inkrementelle und Schreiboperationen durchzuführen. Der Wert des finalen Zählers beträgt 2. Das ist nicht richtig, beide Threads haben eine Erhöhungsoperation durchgeführt, und das korrekte Ergebnis sollte 3 sein. Es ist also einfach unsicher, es als flüchtig zu bezeichnen.
Es ist besser
lock(this.locker) this.counter++;
So ist es sicher (denk natürlich daran, überall zu verriegeln, wo du auf diesen Theke zugreifen möchtest). Es verhindert, dass andere Threads den gesperrten Code ausführen. Und es verhindert auch das oben erwähnte Problem der Multi-CPU-Befehlssequenzierung. Das Problem ist, dass Lock in der Performance langsam ist, und wenn du Lock an anderen, nicht verwandten Stellen verwendest, kann es deine anderen Threads blockieren.
Bestes
Interlocked.Increment (ref this.counter);
Das ist sicher und sehr effizient. Es führt Lesen, Erhöhen und Schreiben drei Operationen in einem Atom aus, ohne in der Mitte unterbrochen zu werden. Da es andere Vorschriften nicht beeinflusst, musst du dir keine Schlösser anderswo merken. Und es ist auch sehr schnell (wie MSDN sagt, ist es auf heutigen CPUs oft nur eine Anweisung).
Aber ich bin mir nicht ganz sicher, ob es auch das Problem der CPU-Befehlsreihenfolge lösen kann oder ob es in Kombination mit flüchtigem Zustand und diesem Inkrementement verwendet werden muss.
Supplement: Welche Probleme löst flüchtiger Stoff gut?
Da volatile Multithreading nicht verhindern kann, was kann es tun? Ein gutes Beispiel ist, dass du zwei Threads hast: Einer schreibt immer zu einer Variable, nehmen wir an, diese Variable ist queneLength, und der andere liest immer Daten aus dieser Variable. Wenn queueLenght nicht flüchtig ist, kann Thread A 5 Mal lesen, aber Thread B kann verzögerte Daten oder sogar Daten in der falschen Reihenfolge sehen. Eine Lösung ist Lock, aber in diesem Fall kannst du auch volatile verwenden. Das stellt sicher, dass Thread B immer die neuesten Daten sieht, die von Thread A geschrieben wurden, aber diese Logik funktioniert nur, wenn man sie beim Schreiben nicht liest, und wenn man sie nicht beim Lesen schreibt. Wenn mehrere Threads Lesen-Modifizieren-Schreib-Operationen durchführen wollen, musst du Interlocked oder Lock verwenden.
|