Question

J'ai un dictionnaire < chaîne, un objet > .

EDIT: On m'a fait remarquer que mon exemple était mauvais. Mon intention n'était pas de mettre à jour les références dans une boucle, mais de mettre à jour différentes valeurs en fonction des différents threads nécessaires pour mettre à jour / obtenir les données. J'ai changé la boucle en méthode.

Je dois mettre à jour les éléments de mon dictionnaire - une clé à la fois et je me demandais s'il y avait des problèmes d'utilisation du verrou sur la valeur .key de mon objet Dictionary?

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

Est-ce que cela résiste au tribunal ou échoue? Je veux juste que chaque valeur du dictionnaire soit verrouillée de manière indépendante afin que verrouiller (et mettre à jour) une valeur ne verrouille pas les autres. De plus, je suis conscient que le verrouillage sera maintenu pendant une longue période - mais les données ne seront pas valides jusqu'à ce qu'elles soient complètement mises à jour.

Était-ce utile?

La solution

Verrouiller un objet accessible en dehors du code constitue un gros risque. Si un autre code (n'importe où) verrouille cet objet, vous risquez de rencontrer des blocages difficiles à déboguer. Notez également que vous verrouillez l’objet , et non la référence. Par conséquent, si je vous ai donné un dictionnaire, je peux toujours conserver les références aux clés et les verrouiller, ce qui nous oblige à verrouiller le même objet.

Si vous encapsulez complètement le dictionnaire et générez vous-même les clés (elles ne sont jamais transmises), vous pouvez être en sécurité.

Cependant, essayez de vous en tenir à une règle: limitez autant que possible la visibilité des objets que vous verrouillez sur le code de verrouillage lui-même.

C'est pourquoi vous voyez ceci:

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

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

plutôt que de voir la ligne A ci-dessus remplacée par

  lock(this)

Ainsi, un objet séparé est verrouillé et la visibilité est limitée.

Modifier Jon Skeet a correctement observé que lockObj ci-dessus devait être en lecture seule.

Autres conseils

Non, cela ne fonctionnerait pas.

La raison en est interner une chaîne . Cela signifie que:

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

sont le même objet! Par conséquent, vous ne devez jamais verrouiller les chaînes, car si une autre partie du programme (une autre instance de ce même objet, par exemple) souhaite également verrouiller la même chaîne, vous pouvez créer accidentellement un conflit de verrouillage qui n’est pas nécessaire; peut-être même une impasse.

N'hésitez pas à le faire avec des non-chaînes, cependant. Par souci de clarté, je prends l'habitude de toujours créer un objet de verrouillage distinct:

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

Je vous recommande de faire la même chose. Créez un dictionnaire avec les objets de verrouillage pour chaque cellule de la matrice. Verrouillez ensuite ces objets si nécessaire.

PS. Changer la collection que vous parcourez n’est pas considéré comme très agréable. Il lancera même une exception avec la plupart des types de collection. Essayez de refactoriser ceci - par exemple itérer sur une liste de clés, si elle sera toujours constante, pas les paires.

Remarque: je suppose que l'exception lorsque la modification de la collection pendant l'itération est déjà fixée

Le dictionnaire n'est pas une collection thread-safe, ce qui signifie qu'il n'est pas sûr de modifier et de lire la collection à partir de différents threads sans synchronisation externe. Hashtable est (était?) Thread-safe pour le scénario one-writer-many-readers, mais Dictionary possède une structure de données interne différente et n'hérite pas de cette garantie.

Cela signifie que vous ne pouvez pas modifier votre dictionnaire en y accédant en lecture ou en écriture à partir de l’autre thread, il peut simplement casser les structures de données internes. Verrouiller la clé ne protège pas la structure de données interne, car lors de la modification de cette clé, une personne pourrait lire une clé différente de votre dictionnaire dans un autre thread. Même si vous pouvez garantir que toutes vos clés sont les mêmes objets (comme dit à propos de l'internalisation de chaînes), cela ne vous met pas en sécurité. Exemple:

  1. Vous verrouillez la clé et commencez à modifier le dictionnaire
  2. Un autre thread tente d'obtenir une valeur pour la clé qui tombe dans le même compartiment que la clé verrouillée. Ce n'est pas seulement lorsque les codes de hachage de deux objets sont identiques, mais plus fréquemment lorsque le hashcode% tableSize est identique.
  3. Les deux threads accèdent au même compartiment (liste liée de clés avec le même hashcode% tableSize value)

S'il n'y a pas de clé de ce type dans le dictionnaire, le premier thread commencera à modifier la liste, et le second thread risque de lire un état incomplet.

Si une telle clé existe déjà, les détails de l’implémentation du dictionnaire pourraient toujours modifier la structure des données, par exemple déplacer les clés récemment utilisées en tête de la liste pour une récupération plus rapide. Vous ne pouvez pas compter sur les détails de la mise en œuvre.

Il existe de nombreux cas comme celui-ci, où vous aurez un dictionnaire corrompu. Vous devez donc avoir un objet de synchronisation externe (ou utiliser Dictionary lui-même, s'il n'est pas exposé au public) et le verrouiller pendant toute l'opération. Si vous avez besoin de verrous plus granulaires lorsque le fonctionnement peut durer un certain temps, vous pouvez copier les clés à mettre à jour, les parcourir, verrouiller le dictionnaire entier lors de la mise à jour par clé unique (n'oubliez pas de vérifier que la clé est toujours là) et le relâcher. laissez les autres threads s'exécuter.

Si je ne me trompe pas, l'intention initiale était de verrouiller un seul élément plutôt que de verrouiller tout le dictionnaire (comme le verrou au niveau de la table ou le verrouillage au niveau de la ligne dans une base de données)

vous ne pouvez pas verrouiller la clé du dictionnaire comme beaucoup l'ont expliqué ici.

Ce que vous pouvez faire est de conserver un dictionnaire interne des objets de verrouillage, qui correspond au dictionnaire actuel. Ainsi, lorsque vous souhaitez écrire dans YourDictionary [Key1], vous devez d'abord verrouiller InternalLocksDictionary [Key1]. Ainsi, un seul thread écrit dans YourDictionary.

Un exemple (pas trop propre) peut être trouvé ici .

Je viens de découvrir cela et je pensais que le code d’identité partageait le code que j’avais écrit il ya quelques années et que j’avais besoin d’un dictionnaire basé sur une clé

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

la classe de verrouillage

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

Dans votre exemple, vous ne pouvez pas faire ce que vous voulez faire!

Vous obtiendrez une exception System.InvalidOperationException avec le message La collection a été modifiée. l'opération d'énumération peut ne pas s'exécuter.

Voici un exemple pour prouver:

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!");
        }
    }   
}

Le résultat serait:

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()

J'y vois quelques problèmes potentiels:

  1. les chaînes peuvent être partagées, vous ne savez donc pas nécessairement qui d'autre peut verrouiller cet objet clé pour quelle autre raison
  2. les chaînes peuvent ne pas être partagées: vous pouvez verrouiller une clé de chaîne avec la valeur "Key1". et un autre morceau de code peut avoir un objet chaîne différent qui contient également les caractères "Key1". Pour le dictionnaire, ce sont la même clé, mais en ce qui concerne le verrouillage, ce sont des objets différents.
  3. Ce verrouillage n'empêchera pas les modifications des objets de valeur eux-mêmes, c'est-à-dire matrixElements [someKey] .ChangeAllYourContents ()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top