109 votes

Ce qui ' s la meilleure façon de mettre en œuvre un dictionnaire thread-safe ?

J’ai réussi à mettre en œuvre un dictionnaire thread-safe en c# en dérivant de IDictionary et définition d’un objet SyncRoot privé :

J’ai ensuite bloquer sur cet objet SyncRoot tout au long de mes consommateurs (plusieurs threads) :

Exemple :

J’ai été capable de le faire fonctionner, mais il en est résulté un code laid. Ma question est, y a-t-il une façon meilleure et plus élégante de mettre en œuvre un dictionnaire thread-safe ?

207voto

Hector Correa Points 10408

La classe .NET 4.0 prend en charge l’accès concurrentiel est appelée ConcurrentDictionary

http://msdn.Microsoft.com/en-us/library/dd287191.aspx

63voto

Greg Beech Points 55270

De tenter de synchroniser l'interne sera presque certainement être insuffisant car il est à un niveau trop bas de l'abstraction. Dire que vous faites l' Add et ContainsKey opérations thread-safe comme suit:

public void Add(TKey key, TValue value)
{
    lock (this.syncRoot)
    {
        this.innerDictionary.Add(key, value);
    }
}

public bool ContainsKey(TKey key)
{
    lock (this.syncRoot)
    {
        return this.innerDictionary.ContainsKey(key);
    }
}

Puis ce qui se passe lorsque vous appelez cette soi-disant "thread-safe" peu de code à partir de plusieurs threads? Il travaille toujours OK?

if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

La réponse simple est non. À un certain point l' Add méthode lève une exception indiquant que la clé existe déjà dans le dictionnaire. Comment cela peut-il être avec un dictionnaire thread-safe, vous pourriez demander? Eh bien tout simplement parce que chaque opération est thread-safe, la combinaison de deux opérations n'est pas, comme un autre thread pourrait modifier entre votre appel à l' ContainsKey et Add.

Ce qui signifie écrire ce type de scénario correctement, vous avez besoin d'un verrou à l'extérieur le dictionnaire, par exemple

lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

Mais maintenant, en voyant que vous êtes d'avoir à écrire de l'extérieur à un code de verrouillage, vous êtes de mélange interne et externe de synchronisation, ce qui conduit toujours à des problèmes tels que floues code et de blocages. Si, finalement, vous êtes probablement mieux à soit:

  1. Utiliser une normale Dictionary<TKey, TValue> et la synchronisation en externe, en joignant le composé opérations sur elle, ou

  2. Écrire un nouveau thread-safe wrapper avec une interface différente (c'est à dire pas IDictionary<T>) qui combine les opérations telles que l' AddIfNotContained méthode vous n'avez jamais besoin de combiner des opérations.

(J'ai tendance à aller avec le n ° 1 de moi-même)

43voto

fryguybob Points 2886

Comme Pierre l'a dit, vous pouvez encapsuler tout le fil de sécurité à l'intérieur de la classe. Vous devez être prudent avec les événements de vous exposer ou d'ajouter, en s'assurant que ils se invoquée à l'extérieur de tous les verrous.

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}

Edit: La MSDN docs point que l'énumération est intrinsèquement pas thread-safe. Qui peut être l'une des raisons pour exposer un objet de synchronisation à l'extérieur de votre classe. Une autre approche serait de fournir des méthodes pour l'exécution d'une action sur tous les membres et de verrouillage autour de l'énumération des membres. Le problème avec ceci est que vous ne savez pas si l'action passée pour que les appels de fonction de certains membres de votre dictionnaire (ce qui conduirait à un blocage). Exposer l'objet de synchronisation permet au consommateur de prendre ces décisions et de ne pas cacher la situation de blocage à l'intérieur de votre classe.

6voto

Jonathan Webb Points 1228

Vous ne devrait pas publier votre objet de verrouillage privé via une propriété. L’objet lock devrait exister privé dans le seul but d’agir comme un point de rendez-vous.

Si les performances s’avère médiocre à l’aide de la serrure standard collection de Power Threading de Wintellect de verrous peut être très utile.

5voto

JaredPar Points 333733

Il y a plusieurs problèmes avec la mise en œuvre de la méthode que vous décrivez.

  1. Vous ne devriez jamais exposer votre objet de synchronisation. Alors vous-même pour un consommateur de l'accaparement de l'objet et de prendre un verrou sur elle et puis vous vous êtes grillé.
  2. Vous êtes à la mise en œuvre d'un non-thread-safe interface avec un "thread-safe" de la classe. À mon humble avis cela va vous coûter en bas de la route

Personnellement, j'ai trouvé la meilleure façon de mettre en œuvre un thread safe classe est par l'intermédiaire de l'immuabilité. Il a vraiment réduit le nombre de problèmes que vous pouvez rencontrer avec filet de sécurité. Découvrez Eric Lippert du Blog pour plus de détails.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X