Question

Je ne pouvais pas trouver assez d'informations sur les types de ConcurrentDictionary, donc je pensais que je demande à ce sujet ici.

À l'heure actuelle, j'utilise un Dictionary pour tenir tous les utilisateurs qui accède en permanence par plusieurs threads (à partir d'un pool de threads, donc pas de montant exact de fils), et il a accès synchronisé.

J'ai récemment découvert qu'il y avait un ensemble de collections thread-safe dans .NET 4.0, et il semble être très agréable. Je me demandais, quel serait le « plus efficace et plus facile à gérer » l'option, que j'ai l'option entre avoir un accès Dictionary normale synchronisée ou un ConcurrentDictionary qui est déjà thread-safe.

Référence à .NET 4.0 de la ConcurrentDictionary

Était-ce utile?

La solution

Une collection de threadsafe par rapport à une non-threadsafe-collection peut être considérée d'une manière différente.

Considérons un magasin sans employé, sauf à la caisse. Vous avez une tonne de problèmes si les gens n'agissent pas de façon responsable. Par exemple, supposons que le client prend une boîte d'une pyramide peut alors qu'un employé est en train de construire la pyramide, tout l'enfer se déchaîner. Ou, si deux clients atteint pour le même article en même temps, qui gagne? Y aura-t un combat? Ceci est un non-threadsafe collecte. Il y a beaucoup de façons d'éviter des problèmes, mais ils ont tous besoin d'une sorte de verrouillage, ou un accès assez explicite d'une manière ou d'une autre.

D'autre part, envisager un magasin avec un employé à un bureau, et vous ne pouvez magasiner à travers lui. Vous obtenez en ligne, et demandez-lui un point, il revenir à vous apporte, et vous sortez de la ligne. Si vous avez besoin de plusieurs éléments, vous ne pouvez prendre autant d'articles sur chaque aller-retour pour que vous vous souvenez, mais vous devez être prudent pour éviter monopolisant le greffier, cela en colère les autres clients en ligne derrière vous.

Considérons maintenant cela. Dans le magasin avec un commis, si vous obtenez tout le chemin à l'avant de la ligne, et demander au greffier « Avez-vous une papier toilette », et il dit: « Oui », puis vous allez « Ok, je ll vous revenir quand je sais combien j'ai besoin », puis au moment où vous êtes de retour à l'avant de la ligne, le magasin peut bien sûr être vendu. Ce scénario n'est pas empêché par une collection threadsafe.

A threadsafe collection garantit que ses structures de données internes sont valables à tout moment, même si accessible à partir de plusieurs threads.

Une collection non-threadsafe ne vient pas avec de telles garanties. Par exemple, si vous ajoutez quelque chose à un arbre binaire sur un fil, tandis qu'un autre fil est occupé rééquilibrer l'arbre, il n'y a aucune garantie l'élément sera ajouté, ou même que l'arbre est toujours valable après, il pourrait être corrompu au-delà de l'espoir.

Une collection threadsafe ne garantit toutefois que les opérations séquentielles sur le fil tout le travail sur le même « instantané » de sa structure interne de données, ce qui signifie que si vous avez du code comme ceci:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

vous pourriez obtenir un NullReferenceException parce inbetween tree.Count et tree.First(), un autre thread a effacé les autres noeuds dans l'arbre, ce qui signifie First() retournera null.

Pour ce scénario, vous devez soit pour voir si la collection en question a un moyen sûr d'obtenir ce que vous voulez, peut-être vous avez besoin de réécrire le code ci-dessus, ou vous pourriez avoir besoin de verrouiller.

Autres conseils

Vous devez toujours être très prudent lors de l'utilisation des collections thread-safe car thread-safe ne signifie pas que vous pouvez ignorer tous les problèmes de filetage. Quand une collection s'annonce comme thread-safe, cela signifie généralement qu'il reste dans un état cohérent, même lorsque plusieurs threads sont la lecture et l'écriture en même temps. Mais cela ne signifie pas qu'un seul thread voir une séquence « logique » des résultats si elle appelle plusieurs méthodes.

Par exemple, si vous devez d'abord vérifier si une clé existe et obtenez plus tard, la valeur qui correspond à la clé, cette clé ne peut plus exister même avec une version ConcurrentDictionary (car un autre thread aurait pu retirer la clé). Vous devez toujours utiliser le verrouillage dans ce cas (ou mieux: combiner les deux appels en utilisant TryGetValue ).

Alors ne les utiliser, mais ne pense pas que cela vous donne un laissez-passer d'ignorer tous les problèmes de concurrence. Vous devez toujours être prudent.

ConcurrentDictionary interne utilise un verrou séparé pour chaque compartiment de hachage. Tant que vous utilisez uniquement Ajouter / TryGetValue et les méthodes qui fonctionnent comme des entrées individuelles, le dictionnaire fonctionnera comme une structure de données presque sans verrouillage avec l'avantage de la performance douce respective. OTOH les méthodes de dénombrement (y compris la propriété Count) verrouiller tous les seaux à la fois et sont donc pire qu'un dictionnaire synchronisé, en terme de performance.

Je dirais, il suffit d'utiliser ConcurrentDictionary.

Je pense que la méthode ConcurrentDictionary.GetOrAdd est exactement ce que la plupart des scénarios multi-thread ont besoin.

Avez-vous vu réactive Extensions pour .Net 3.5sp1. Selon Jon Skeet, ils ont rétroportés un paquet des extensions parallèles et des structures de données simultanées pour .Net3.5 sp1.

Il y a un ensemble d'échantillons pour .Net 4 Beta 2, qui décrit assez bien les détails sur la façon de les utiliser les extensions parallèles.

Je viens de passer la dernière semaine tester le ConcurrentDictionary en utilisant 32 threads pour effectuer des E / S. Il semble fonctionner comme prévu, ce qui indiquerait une énorme quantité de tests a été mis.

Modifier :. NET 4 ConcurrentDictionary et modèles

Microsoft a publié un pdf appelé modèles de programmation Paralell. Son téléchargement reallly vaut la peine car il décrit en détail les modèles vraiment sympa droit d'utiliser pour .Net 4 extensions simultanées et les modèles pour éviter les anti. Ici, il est.

Fondamentalement, vous voulez aller avec la nouvelle ConcurrentDictionary. Dès la sortie de la boîte, vous devez écrire moins de code pour faire du fil des programmes de sécurité.

Nous avons utilisé pour la collecte ConcurrentDictionary mis en cache, qui est repeuplé toutes les 1 heure, puis lu par plusieurs threads client, similaire à solution pour le cet exemple est-thread-safe? question .

Nous avons trouvé que changer ReadOnlyDictionary l'amélioration de la performance globale .

Le ConcurrentDictionary est une excellente option si elle remplit tous les vos besoins fil de sécurité. Si ce n'est pas, autrement dit de vous font quoi que ce soit Dictionary un peu complexe, une normale + lock peut être une meilleure option. Par exemple permet de dire que vous ajoutez des commandes dans un dictionnaire, et que vous voulez tenir à jour le montant total des commandes. Vous pouvez écrire du code comme ceci:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

Ce code est pas thread-safe. plusieurs threads mise à jour le champ _totalAmount peut le laisser dans un état corrompu. Ainsi, vous pouvez essayer de le protéger avec un lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

Ce code est "plus sûr", mais toujours pas thread-safe. Il n'y a aucune garantie que le _totalAmount est compatible avec les entrées dans le dictionnaire. Un autre thread peut essayer de lire ces valeurs, mettre à jour un élément d'interface utilisateur:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

Le totalAmount peut ne pas correspondre au nombre de commandes dans le dictionnaire. Les statistiques affichées pourraient se tromper. À ce stade, vous vous rendrez compte que vous devez étendre la protection de lock pour inclure la mise à jour du dictionnaire:

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

Ce code est parfaitement sûr, mais tous les avantages de l'utilisation d'un ConcurrentDictionary sont partis.

  1. La performance est devenue pire que d'utiliser un Dictionary normal, parce que le verrouillage interne à l'intérieur du ConcurrentDictionary est maintenant inutile et redondant.
  2. Vous devez examiner tout votre code pour des utilisations non protégées des variables partagées.
  3. Vous êtes coincé avec l'aide d'une API maladroite (TryAdd ?, AddOrUpdate?).

Donc, mon conseil est: commencer par un Dictionary + lock, et garder la possibilité de mise à niveau plus tard à un ConcurrentDictionary comme l'optimisation des performances, si cette option est réellement viable. Dans de nombreux cas, il ne sera pas.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top