2008-10-01 2 views
8

Ich habe eine Dictionary<string, someobject>.Verwenden der Sperre für den Schlüssel eines Dictionary <string, Objekt>

EDIT: Es wurde darauf hingewiesen, dass mein Beispiel schlecht war. Meine ganze Absicht war nicht, die Referenzen in einer Schleife zu aktualisieren, sondern um verschiedene Werte zu aktualisieren, die auf unterschiedlichen Threads basieren, müssen die Daten aktualisieren/abrufen. Ich habe die Schleife in eine Methode geändert.

Ich muss Elemente in meinem Wörterbuch aktualisieren - ein Schlüssel nach dem anderen und ich fragte mich, ob es Probleme bei der Verwendung der Sperre für den .key-Wert meines Dictionary-Objekts gibt?

private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>(); 

//Pseudo-code 

public static void UpdateValue(string key) 
{ 
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key]; 
    lock (keyValuePair.Key) 
    { 
     keyValuePair.Value = SomeMeanMethod(); 
    } 
} 

Würde das vor Gericht bestehen oder scheitern? Ich möchte nur, dass jeder Wert im Wörterbuch unabhängig gesperrt wird, sodass das Sperren (und Aktualisieren) eines Wertes die anderen nicht sperrt. Ich bin mir auch bewusst, dass die Sperre für eine lange Zeit hält - aber die Daten werden ungültig, bis sie vollständig aktualisiert werden.

+0

keine Antwort, aber Sie wissen, dass, wenn Sie bereits das Paar haben, Sie pair.Value verwenden können, anstatt den Operator [] erneut im Dictionary aufzurufen? – OregonGhost

+0

Eigentlich habe ich versucht, aber der Wert ist schreibgeschützt oder so meine IDE sagte mir? –

+0

Sie können foreach Elemente nicht ändern – sieben

Antwort

11

Das Sperren auf einem Objekt, das außerhalb der Codeverriegelung zugänglich ist, ist ein großes Risiko. Wenn irgendein anderer Code (irgendwo) jemals dieses Objekt sperrt, könnte es zu einigen Deadlocks kommen, die schwer zu debuggen sind. Beachten Sie auch, dass Sie das Objekt, nicht die Referenz sperren. Wenn ich Ihnen also ein Wörterbuch gebe, kann ich immer noch Referenzen auf die Schlüssel halten und sie sperren - wodurch wir das gleiche Objekt sperren.

Wenn Sie vollständig das Wörterbuch kapseln, und erzeugen die Schlüssel selbst (sie sind nicht immer übergeben werden, dann können Sie sicher sein

jedoch versuchen, eine Regel zu bleiben -. Begrenzen die Sichtbarkeit die Objekte, die Sie sperren auf den Sperrcode selbst, wann immer möglich

das ist, warum Sie diese sehen.

public class Something 
{ 
    private readonly object lockObj = new object(); 

    public SomethingReentrant() 
    { 
    lock(lockObj) // Line A 
    { 
     // ... 
    } 
    } 
} 

anstatt Linie A sehen über 0 durch

ersetzt
lock(this) 

Auf diese Weise ist ein separates Objekt gesperrt, und die Sichtbarkeit ist begrenzt.

BearbeitenJon Skeet richtig beobachtet, dass lockObj oben sollte nur gelesen werden.

+6

Ich würde hinzufügen, dass LockObj fast immer auch Readonly deklariert werden sollte. –

8

Nein, das würde nicht funktionieren.

Der Grund ist string interning. Das bedeutet:

string a = "Something"; 
string b = "Something"; 

sind beide das gleiche Objekt! Daher sollten Sie keine Strings sperren, da Sie, wenn ein anderer Teil des Programms (z. B. eine andere Instanz desselben Objekts) ebenfalls dieselbe Zeichenfolge sperren möchte, versehentlich Sperrkonflikte erstellen könnten, wo dies nicht erforderlich ist. möglicherweise sogar eine Sackgasse.

Fühlen Sie sich frei, dies mit Nicht-Strings zu tun, obwohl. Für die beste Klarheit, ich mache es eine persönliche Gewohnheit, immer ein separates Sperrobjekt zu erstellen:

class Something 
{ 
    bool threadSafeBool = true; 
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool 
} 

empfehle ich Ihnen das Gleiche tun. Erstellen Sie ein Dictionary mit den Sperrobjekten für jede Matrixzelle. Sperren Sie diese Objekte dann bei Bedarf.

PS. Das Ändern der Sammlung, die Sie durchlaufen, wird nicht als sehr gut angesehen. Es wird sogar eine Ausnahme mit den meisten Sammlungstypen auslösen. Versuchen Sie, dies neu zu gestalten - z. Iteriere über eine Liste von Schlüsseln, wenn sie immer konstant ist, nicht die Paare.

+0

Sie können auch String Interning deaktivieren - was ich glaube Alle .NET Framework Assemblies tun dies. –

+1

Der "Schlüssel" zu einem Wörterbuch kann jedes Objekt sein, nicht nur ein String! Dies würde nicht gelten, wenn der Schlüssel eine Ganzzahl wäre! –

+1

Es ist wichtig zu beachten, dass nur String-Literale implizit interniert sind. Man muss string.Intern() für andere Strings verwenden. Ich stimme völlig mit Ihrem Punkt überein, dass Zeichenfolgen für diese Art von Sache unsicher sind, weil jede gegebene Zeichenfolge interniert werden kann, die subtile Probleme beim Sperren verursachen könnte. –

0

In Ihrem Beispiel können Sie nicht tun, was Sie tun möchten!

Sie erhalten eine System.InvalidOperationException mit einer Nachricht von Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt. Hier

ist ein Beispiel zu beweisen:

using System.Collections.Generic; 
using System; 

public class Test 
{ 
    private Int32 age = 42; 

    static public void Main() 
    { 
     (new Test()).TestMethod(); 
    } 

    public void TestMethod() 
    { 
     Dictionary<Int32, string> myDict = new Dictionary<Int32, string>(); 

     myDict[age] = age.ToString(); 

     foreach(KeyValuePair<Int32, string> pair in myDict) 
     { 
      Console.WriteLine("{0} : {1}", pair.Key, pair.Value); 
      ++age; 
      Console.WriteLine("{0} : {1}", pair.Key, pair.Value); 
      myDict[pair.Key] = "new"; 
      Console.WriteLine("Changed!"); 
     } 
    } 
} 

Der Ausgang wäre:

42 : 42 
42 : 42 

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute. 
    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) 
    at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext() 
    at Test.TestMethod() 
    at Test.Main() 
+0

Ich möchte nicht das gesamte Wörterbuch sperren - nur den Schlüsselpaarwert -, also würden keine zwei Prozesse den gleichen Wert im Schlüsselpaarwert gleichzeitig aktualisieren. –

+0

Ich change nicht den Schlüssel - nur der Wert, also sollte es kein Problem sein –

0

ich ein paar mögliche Probleme dort sehen:

  1. Strings können sein shared, damit Sie nicht unbedingt wissen, wer sonst dieses Schlüsselobjekt für welches andere reaso sperren könnte n
  2. Strings möglicherweise nicht geteilt werden: Sie können auf einen String-Schlüssel mit dem Wert "Key1" sperren und ein anderes Stück Code möglicherweise ein anderes String-Objekt, das auch die Zeichen "Key1" enthält. Für das Wörterbuch sind sie derselbe Schlüssel, aber was das Sperren betrifft, sind sie verschiedene Objekte.
  3. Das Verriegelungs wird keine Änderungen an den Wert verhindern Objekte selbst, dh matrixElements[someKey].ChangeAllYourContents()
3

Hinweis: Ich gehe davon aus, wenn Ausnahme während der Iteration Sammlung Modifizierung bereits fixiert ist

Wörterbuch ist nicht Thread-sicher Sammlung Das bedeutet, es ist nicht sicher zu ändern und Sammlung von verschiedenen Threads ohne externe Synchronisation zu lesen. Hashtable ist (war?) Thread-sicher für ein Schreiber-viele-Leser-Szenario, aber Dictionary hat andere interne Datenstruktur und erbt diese Garantie nicht.

Das bedeutet, dass Sie Ihr Wörterbuch nicht ändern können, während Sie auf es zum Lesen oder Schreiben von dem anderen Thread zugreifen, es kann nur interne Datenstrukturen beschädigt. Durch das Sperren des Schlüssels wird die interne Datenstruktur nicht geschützt, denn während Sie diesen Schlüssel ändern, könnte jemand einen anderen Schlüssel Ihres Wörterbuchs in einem anderen Thread lesen. Selbst wenn Sie garantieren können, dass alle Ihre Schlüssel die gleichen Objekte sind (wie beim String-Interning), bringt Sie dies nicht auf die sichere Seite. Beispiel:

  1. Sie den Schlüssel sperren und beginnen Wörterbuch
  2. Ein anderer Thread versucht, ändern Wert für den Schlüssel zu erhalten, die in den gleichen Eimer eine als gesperrt fallen passiert. Dies ist nicht nur der Fall, wenn die Hashcodes zweier Objekte gleich sind, sondern häufiger, wenn der Hashcode% tableSize gleich ist.
  3. beiden Threads zugreifen denselben bucket (verkettete Liste von Schlüsseln mit dem Wert gleiches Hashcode% table)

Wenn kein solcher Schlüssel im Wörterbuch ist, starten erster Thread die Liste zu modifizieren, und den zweiten Thread wahrscheinlich, unvollständiger Zustand zu lesen.

Wenn ein solcher Schlüssel bereits vorhanden ist, können Implementierungsdetails des Wörterbuchs die Datenstruktur noch ändern, z. B. können zuletzt aufgerufene Schlüssel zum schnelleren Abrufen an den Anfang der Liste verschoben werden. Sie können sich nicht auf Implementierungsdetails verlassen.

Es gibt viele Fälle wie diese, wenn Sie Wörterbuch beschädigt haben. Sie müssen also ein externes Synchronisationsobjekt haben (oder das Dictionary selbst verwenden, wenn es nicht öffentlich zugänglich ist) und es während des gesamten Vorgangs sperren. Wenn Sie mehr granulare Sperren benötigen, wenn die Operation einige Zeit in Anspruch nehmen kann, können Sie die zu aktualisierenden Schlüssel kopieren, darüber iterieren, das gesamte Wörterbuch während der Einzelschlüsselaktualisierung sperren (vergessen Sie nicht, den Schlüssel noch immer zu überprüfen) und freigeben Lass andere Threads laufen.

2

Wenn ich mich nicht irre, war die ursprüngliche Absicht auf ein einzelnes Element zu sperren, anstatt das gesamte Wörterbuch Sperren (wie Sperre auf Tabellenebene vs. Zeilenebene Sperre in einem DB)

Sie können‘ t Sperren Sie den Schlüssel des Wörterbuchs so viele hier erklärt.

Was Sie tun können, ist, ein internes Wörterbuch von Sperrobjekten zu behalten, das dem tatsächlichen Wörterbuch entspricht. Wenn Sie also in YourDictionary [Key1] schreiben möchten, sperren Sie zuerst InternalLocksDictionary [Key1] - also schreibt nur ein einziger Thread in YourDictionary.

ein (nicht zu sauber) Beispiel kann found here sein.

0

kam gerade über diese und dachte einige Code-ID Anteil ich vor ein paar Jahren schrieb, wo ich auf einem Schlüssel Basis in ein Wörterbuch benötigt

using (var lockObject = new Lock(hashedCacheID)) 
{ 
    var lockedKey = lockObject.GetLock(); 
    //now do something with the dictionary 
} 

der Schlossklasse

class Lock : IDisposable 
    { 
     private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>(); 

     private static readonly object CritialLock = new object(); 

     private readonly string _key; 
     private bool _isLocked; 

     public Lock(string key) 
     { 
      _key = key; 

      lock (CritialLock) 
      { 
       //if the dictionary doesnt contain the key add it 
       if (!Lockedkeys.ContainsKey(key)) 
       { 
        Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references 
       } 
      } 
     } 

     public string GetLock() 
     { 
      var key = Lockedkeys[_key]; 

      if (!_isLocked) 
      { 
       Monitor.Enter(key); 
      } 
      _isLocked = true; 

      return key; 
     } 

     public void Dispose() 
     { 
      var key = Lockedkeys[_key]; 

      if (_isLocked) 
      { 
       Monitor.Exit(key); 
      } 
      _isLocked = false; 
     } 
    } 
Verwandte Themen