9 Stimmen

Sperre für den Schlüssel eines Dictionary<string, object> verwenden

Ich habe eine Dictionary<string, someobject> .

EDIT: Ich wurde darauf aufmerksam gemacht, dass mein Beispiel schlecht war. Meine Absicht war es nicht, die Referenzen in einer Schleife zu aktualisieren, sondern verschiedene Werte zu aktualisieren, je nachdem, welche Threads die Daten aktualisieren/abrufen müssen. Ich habe die Schleife in eine Methode geändert.

Ich brauche, um Elemente in meinem Wörterbuch zu aktualisieren - ein Schlüssel zu einer Zeit, und ich wunderte mich, wenn es irgendwelche Probleme bei der Verwendung der Sperre auf den Wert .key 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 Bestand haben oder scheitern? Ich möchte nur, dass jeder Wert im Wörterbuch unabhängig gesperrt wird, so dass das Sperren (und Aktualisieren) eines Wertes die anderen nicht sperrt. Auch ich bin mir bewusst, die Sperre wird für eine lange Zeit halten - aber die Daten werden ungültig sein, bis vollständig aktualisiert.

0 Stimmen

Keine Antwort, aber Sie wissen, dass Sie, wenn Sie das Paar bereits haben, pair.Value verwenden können, anstatt operator[] erneut im Dictionary aufzurufen?

0 Stimmen

Ich habe es versucht, aber der Wert ist schreibgeschützt, so hat es mir meine IDE gesagt.

0 Stimmen

Sie können die foreach-Elemente nicht ändern

11voto

Philip Rieck Punkte 31977

Das Sperren eines Objekts, auf das außerhalb des sperrenden Codes zugegriffen werden kann, ist ein großes Risiko. Wenn ein anderer Code (irgendwo) dieses Objekt sperrt, kann es zu Deadlocks kommen, die schwer zu debuggen sind. Beachten Sie auch, dass Sie die objet Wenn ich Ihnen also ein Wörterbuch gebe, kann ich immer noch Verweise auf die Schlüssel halten und auf sie sperren - was dazu führt, dass wir auf dasselbe Objekt sperren.

Wenn Wenn Sie das Wörterbuch vollständig kapseln und die Schlüssel selbst generieren (sie werden nie übergeben), dann sind Sie vielleicht sicher.

Versuchen Sie jedoch, sich an eine Regel zu halten - beschränken Sie die Sichtbarkeit der Objekte, die Sie sperren, auf den Sperrcode selbst, wann immer dies möglich ist.

Deshalb sehen Sie das hier:

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

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

anstatt die obige Zeile A ersetzt zu sehen durch

  lock(this)

Auf diese Weise wird ein separates Objekt gesperrt, und die Sichtbarkeit ist eingeschränkt.

Editar Jon Skeet korrekt festgestellt, dass lockObj oben readonly sein sollte.

7 Stimmen

Ich würde hinzufügen, dass lockObj fast immer auch als schreibgeschützt deklariert werden sollte.

10voto

Sander Punkte 24565

Nein, das würde nicht funktionieren.

Der Grund dafür ist String-Praktikum . Dies bedeutet, dass:

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

sind beide das gleiche Objekt! Daher sollten Sie niemals auf Zeichenketten sperren, denn wenn ein anderer Teil des Programms (z.B. eine andere Instanz desselben Objekts) ebenfalls auf dieselbe Zeichenkette sperren will, könnten Sie versehentlich einen Sperrkonflikt erzeugen, wo kein Bedarf dafür besteht; möglicherweise sogar einen Deadlock.

Sie können dies aber auch mit Nicht-Strings tun. Aus Gründen der Übersichtlichkeit habe ich es mir zur Gewohnheit gemacht, immer ein separates Lock-Objekt zu erstellen:

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

Ich empfehle Ihnen, das Gleiche zu tun. Erstellen Sie ein Dictionary mit den Sperrobjekten für jede Matrixzelle. Dann sperren Sie diese Objekte bei Bedarf.

PS. Das Ändern der Sammlung, über die Sie iterieren, wird nicht als sehr nett angesehen. Bei den meisten Sammlungstypen wird sogar eine Ausnahme ausgelöst. Versuchen Sie, dies zu refaktorisieren - z. B. iterieren über eine Liste von Schlüsseln, wenn es immer konstant sein wird, nicht die Paare.

0 Stimmen

Sie können auch das String-Interning deaktivieren - ich glaube, das tun alle .NET Framework-Assemblies.

1 Stimmen

Der "Schlüssel" eines Wörterbuchs kann ein beliebiges Objekt sein, nicht nur eine Zeichenkette! Dies würde nicht gelten, wenn der Schlüssel ein Integer wäre!

1 Stimmen

Es ist wichtig zu beachten, dass nur String-Literale implizit interniert werden. Für andere Strings muss man string.Intern() verwenden. Ich stimme Ihnen voll und ganz zu, dass Strings für diese Art von Dingen unsicher sind, da jede beliebige Zeichenkette interniert werden kann, was zu subtilen Problemen beim Sperren führen könnte.

3voto

Ilya Ryzhenkov Punkte 11342

Hinweis: Ich gehe davon aus, dass die Ausnahme beim Ändern der Sammlung während der Iteration bereits behoben ist

Dictionary ist keine thread-sichere Sammlung, d.h. es ist pas sicher zu ändern und zu lesen Sammlung von verschiedenen Threads ohne externe Synchronisation. Hashtable ist (war?) thread-sicher für das Szenario "ein Schreiber, viele Leser", aber Dictionary hat eine andere interne Datenstruktur und erbt diese Garantie nicht.

Das bedeutet, dass Sie Ihr Wörterbuch nicht ändern können, während Sie von einem anderen Thread darauf zugreifen, um es zu lesen oder zu schreiben, sondern nur interne Datenstrukturen zerstören können. Das Sperren des Schlüssels schützt die interne Datenstruktur nicht, denn während Sie genau 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 bei String-Interning), bringt Sie das nicht auf die sichere Seite. Beispiel:

  1. Sie sperren den Schlüssel und beginnen, das Wörterbuch zu ändern
  2. Ein anderer Thread versucht, einen Wert für den Schlüssel zu erhalten, der zufällig in denselben Bucket wie der gesperrte fällt. Dies geschieht nicht nur, wenn die Hashcodes zweier Objekte gleich sind, sondern häufiger, wenn der Hashcode%tableSize gleich ist.
  3. Beide Threads greifen auf denselben Bucket zu (verknüpfte Liste von Schlüsseln mit demselben Hashcode%tableSize-Wert)

Wenn es keinen solchen Schlüssel im Wörterbuch gibt, beginnt der erste Thread mit der Änderung der Liste, und der zweite Thread wird wahrscheinlich einen unvollständigen Zustand lesen.

Wenn ein solcher Schlüssel bereits vorhanden ist, könnte die Datenstruktur durch Implementierungsdetails des Wörterbuchs noch geändert werden, z. B. durch Verschieben der zuletzt verwendeten Schlüssel an den Anfang der Liste, um sie schneller abrufen zu können. Sie können sich nicht auf Implementierungsdetails verlassen.

Es gibt viele solche Fälle, in denen das Wörterbuch beschädigt ist. Sie müssen also ein externes Synchronisationsobjekt haben (oder das Dictionary selbst verwenden, wenn es nicht öffentlich zugänglich ist) und es während der gesamten Operation sperren. Wenn Sie granularere Sperren benötigen und die Operation lange dauern kann, können Sie die zu aktualisierenden Schlüssel kopieren, darüber iterieren, das gesamte Wörterbuch während der Aktualisierung eines einzelnen Schlüssels sperren (vergessen Sie nicht zu überprüfen, ob der Schlüssel noch vorhanden ist) und die Sperre freigeben, damit andere Threads ausgeführt werden können.

3voto

Wenn ich mich nicht irre, war die ursprüngliche Absicht, auf ein einzelnes Element zu sperren, anstatt das gesamte Wörterbuch zu sperren (wie eine Sperre auf Tabellenebene im Vergleich zu einer Zeilensperre in einer DB)

Sie können den Schlüssel des Wörterbuchs nicht sperren, wie viele hier erklärt haben.

Was Sie tun können, ist ein internes Wörterbuch von Sperrobjekten zu führen, das dem aktuellen Wörterbuch entspricht. Wenn Sie also in YourDictionary[Key1] schreiben wollen, sperren Sie zunächst InternalLocksDictionary[Key1], so dass nur ein einziger Thread in YourDictionary schreiben kann.

ein (nicht zu sauberes) Beispiel kann sein hier gefunden .

1voto

Mark Punkte 1534

Gerade stieß auf diese und dachte id teilen einige Code, den ich schrieb vor ein paar Jahren, wo ich brauchte, um ein Wörterbuch auf Basis eines Schlüssels

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

die Sperrklasse

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;
        }
    }

0 Stimmen

Wenn Sie Sperren für Wörterbuchschlüssel auf der Grundlage von Zeichenketten erzeugen, sollte dies auch nicht funktionieren? Hash-Codes können für verschiedene Zeichenketten kollidieren und Wörterbücher implementieren unter der Haube verknüpfte Hash-Code-Listen, was bedeutet, dass bei größeren Datensätzen mehrere Schlüssel in einer verknüpften Liste mit demselben Hash-Code-Wert in der Implementierung sein können. Wenn Sie in den Datensatz hinzufügen oder tun viel Verarbeitung, die mit dem Wörterbuch funktioniert es möglich ist, können Sie noch in Schreibkollisionen laufen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X