2008-12-08 12 views
8

In einem Multithread-Programm, das auf einem Multi-CPU-Rechner ausgeführt wird, muss ich mit volatilen Lese-/Schreibzugriff auf den freigegebenen Status (_data im folgenden Beispielcode) zugreifen um die Richtigkeit zu gewährleisten.Verwendung von volatile (Thread.VolatileRead/Thread.VolatileWrite) in C#

Mit anderen Worten, können Heap-Objekte auf der CPU zwischengespeichert werden?

Mithilfe des unten stehenden Beispiels und unter der Annahme, dass Multi-Threads auf die GetValue- und Add-Methoden zugreifen, muss ThreadA Daten hinzufügen (mithilfe der Add-Methode) und ThreadB, um die hinzugefügten Daten sofort anzeigen zu können (Verwenden der GetValue-Methode). Muss ich flüchtige Lese-/Schreibvorgänge zu _data hinzufügen, um dies sicherzustellen? Im Grunde möchte ich keine Daten hinzufügen, die auf der CPU von ThreadA zwischengespeichert werden.

/Ich bin nicht Sperren (Durchsetzung exklusiven Thread-Zugriff) als der Code muss ultra-schnell sein und ich bin keine Daten aus _data entfernen, so dass ich _data nicht sperren müssen.

Danke.

**** aktualisieren ****************************

Offensichtlich ihr denken gehen Lock-frei mit Dieses Beispiel ist eine schlechte Idee. Aber welchen Nebenwirkungen oder Ausnahmen könnte ich hier begegnen?

Kann der Dictionary-Typ eine Ausnahme auslösen, wenn 1 Thread die Werte zum Lesen iteriert und ein anderer Thread die Werte für die Aktualisierung iteriert? Oder würde ich nur "Dirty Reads" erleben (was in meinem Fall in Ordnung wäre)?

**** End-Update ****************************

public sealed class Data 
{ 
    private volatile readonly Dictionary<string, double> _data = new Dictionary<string, double>(); 

    public double GetVaule(string key) 
    { 
     double value; 
     if (!_data.TryGetValue(key, out value)) 
     { 
      throw new ArgumentException(string.Format("Key {0} does not exist.", key)); 
     } 
     return value; 
    } 

    public void Add(string key, double value) 
    { 
     _data.Add(key, value); 
    } 

    public void Clear() 
    { 
     _data.Clear(); 
    } 
} 

Vielen Dank für die Antworten. Was die Sperren anbelangt, so werden die Methoden ziemlich oft von mehreren Threads aufgerufen, sodass mein Problem bei umkämpften Sperren nicht die eigentliche Sperroperation ist.

Also ist meine Frage über CPU-Caching, kann Heap-Objekte (die _Data Instanz Feld) auf einer CPU zwischengespeichert werden? Benötige ich den Zugriff auf das Feld _data mit volatilen Lese-/Schreibvorgängen?

/Auch ich bin mit .Net 2.0 stecken.

Danke für Ihre Hilfe.

Antwort

2

Ich glaube nicht, dass volatile ein Ersatz für Sperren sein kann, wenn Sie Methoden aufrufen. Sie garantieren, dass der Thread A und der Thread B die gleiche Kopie des Wörterbuchs sehen, Sie aber trotzdem gleichzeitig auf das Wörterbuch zugreifen können. Sie können Multi-Moded-Sperren verwenden, um die Parallelität zu erhöhen. Siehe zum Beispiel ReaderWriterLockSlim.

Repräsentiert eine Sperre, die auf verwendet wird Zugriff auf eine Ressource verwalten, so dass mehrere Threads zum Lesen oder exklusiven Zugriff für das Schreiben.

16

Die MSDN docs für Dictionary<TKey, TValue> sagen, dass es für mehr Leser sicher ist, aber sie „ein Schriftsteller, mehrere Leser“ Garantie nicht geben, dass einige andere Klassen zu tun. Kurz gesagt, ich würde das nicht tun.

Sie sagen, Sie vermeiden Sperren, weil Sie den Code "ultra-schnell" sein müssen - haben Sie versucht Locking, um zu sehen, was der Aufwand ist? Unangefochtene Schlösser sind sehr billig, und wenn das Schloss umkämpft ist, profitiert man von der zusätzlichen Sicherheit. Ich würde dies sicherlich ausführlich beschreiben, bevor ich mich dazu entscheide, mich um die Nebenläufigkeitsprobleme einer Lock-Free-Lösung zu kümmern. ReaderWriterLockSlim kann nützlich sein, wenn Sie tatsächlich mehrere Leser haben, aber es klingt, als ob Sie einen einzelnen Leser und einen einzigen Schreiber haben, zumindest im Moment - einfaches Sperren wird in diesem Fall einfacher sein.

3

Das Problem ist, dass das Add-Methode:

public void Add(string key, double value) 
{ 
    _data.Add(key, value); 
} 

Könnte _data verursachen, zu entscheiden, um die Daten vollständig neu organisieren es hält - an diesem Punkt eine GetVaule Anfrage in jeder möglichen Weise fehlschlagen könnte.

Sie benötigen eine Sperre oder eine andere Datenstruktur/Datenstrukturimplementierung.

6

Ich denke, Sie können die Verwendung des Schlüsselwortes volatile missverstehen (entweder das oder ich bin, und jemand bitte zögern Sie mich zu korrigieren). Das Schlüsselwort volatile garantiert, dass get und set-Operationen für den Wert der Variablen selbst aus mehreren Threads immer mit derselben Kopie arbeiten. Zum Beispiel, wenn ich einen bool habe, der einen Zustand anzeigt, dann wird der neue Wert sofort für den anderen verfügbar gemacht, wenn man ihn in einen Thread einstellt.

Sie ändern jedoch nie den Wert Ihrer Variablen (in diesem Fall eine Referenz). Sie manipulieren nur den Speicherbereich, auf den die Referenz verweist. Erklärt es als volatile readonly (was, wenn mein Verständnis ist gesund, besiegt den Zweck der volatile, indem es nie erlaubt zu setzen) hat keine Auswirkungen auf die tatsächlichen Daten, die manipuliert wird (der Back-End-Speicher für die Dictionary<>).

Alles, was gesagt wird, müssen Sie in diesem Fall wirklich eine Sperre verwenden. Ihre Gefahr geht über die Aussicht auf "Dirty Reads" hinaus (was bedeutet, dass das, was Sie gelesen haben, wäre zu irgendeinem Zeitpunkt gültig gewesen) in wirklich unbekanntes Gebiet. Wie Jon sagte, Sie brauchen wirklich den Beweis, dass das Sperren eine inakzeptable Leistung erzeugt, bevor Sie versuchen, die Straße der Lockless-Codierung zu gehen. Ansonsten ist das der Inbegriff einer vorzeitigen Optimierung.

0

Volatile sperrt nicht, es hat nichts mit Synchronisation zu tun. Lock-Free-Lesevorgänge bei schreibgeschützten Daten sind im Allgemeinen sicher. Beachten Sie, dass Sie anscheinend _data.Add() aufrufen, nur weil Sie nichts aus _data entfernen. Das ist NICHT schreibgeschützt. Also ja, dieser Code wird in einer Vielzahl von aufregenden und schwer vorhersehbaren Wegen in deinem Gesicht explodieren.

Verwenden Sie Sperren, es ist einfach, es ist sicherer. Wenn du ein Lock-Free-Guru bist (du bist es nicht!), Zeigt das AND-Profiling einen Engpass im Zusammenhang mit der Sperre, UND du kannst die Streitfragen nicht durch Partitionierung oder Wechsel zu Spin-Locks lösen DANN UND NUR DANN kannst du Untersuchen Sie eine Lösung, um blockierungsfreie Lesevorgänge zu erhalten, was bedeutet, dass Sie Ihr eigenes Wörterbuch von Grund auf neu schreiben müssen und möglicherweise schneller sind als die Sperrlösung.

Fangen Sie an zu sehen, wie weit Sie von Ihrem Denken entfernt sind? Benutze einfach ein verdammtes Schloss!

1

Das Schlüsselwort bezieht sich nicht auf das Sperren, es wird verwendet, um anzuzeigen, dass der Wert des angegebenen Felds möglicherweise von einem anderen Thread oder einer anderen Komponente geändert oder gelesen wird, die gleichzeitig mit Ihrem Code ausgeführt werden kann. Dies ist für den Compiler wichtig, da viele Optimierungsprozesse das Zwischenspeichern des Variablenwerts und das Neuanordnen der Anweisungen beinhalten.Das Schlüsselwort volatile weist den Compiler an, "vorsichtig" zu sein, wenn diese Anweisungen optimiert werden, die auf die Variable volatile verweisen.

Für Multi-Thread-Verwendung von Wörterbuch gibt es viele Möglichkeiten zu tun. Die einfachste Methode ist die Verwendung des Schlüsselworts lock, das über eine angemessene Leistung verfügt. Wenn Sie eine höhere Leistung benötigen, müssen Sie möglicherweise ein eigenes Wörterbuch für Ihre spezifische Aufgabe implementieren.