web-dev-qa-db-fra.com

Quelle est l'utilisation correcte de ConcurrentBag?

J'ai déjà lu les questions précédentes sur ConcurrentBag mais je n'ai pas trouvé d'exemple réel d'implémentation dans le multi-threading.

ConcurrentBag est une implémentation de thread-safe bag, optimisée pour les scénarios où le même thread produira et consommera des données stockées dans le bag. "

Actuellement, il s'agit de l'utilisation actuelle dans mon code (il s'agit de codes simplifiés et non réels):

private void MyMethod()
{
    List<Product> products = GetAllProducts(); // Get list of products
    ConcurrentBag<Product> myBag = new ConcurrentBag<Product>();

    //products were simply added here in the ConcurrentBag to simplify the code
    //actual code process each product before adding in the bag
    Parallel.ForEach(
                products,
                new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
                product => myBag.Add(product));

    ProcessBag(myBag); // method to process each items in the concurrentbag
}

Mes questions:
Est-ce la bonne utilisation de ConcurrentBag? Est-il correct d'utiliser ConcurrentBag dans ce type de scénario?

Pour moi, je pense qu'un simple List<Product> et un verrouillage manuel fera mieux. La raison en est que le scénario ci-dessus rompt déjà la règle "le même thread produira et consommera les données stockées dans le sac".
J'ai également découvert que le stockage ThreadLocal créé dans chaque thread en parallèle existera toujours après l'opération (même si le thread est réutilisé, n'est-ce pas?), Ce qui peut provoquer une mémoire indésirable fuite.
Ai-je raison dans ce gars-là? Ou une simple méthode claire ou vide pour supprimer les éléments du ConcurrentBag suffit-elle?

48
hisoka21

Cela ressemble à une utilisation correcte de ConcurrentBag. Les variables locales de thread sont membres du sac et deviendront éligibles pour la collecte des ordures en même temps que le sac (l'effacement du contenu ne les libérera pas). Vous avez raison qu'une simple liste avec un verrou suffirait pour votre cas. Si le travail que vous effectuez dans la boucle est important, le type de synchronisation des threads n'aura pas beaucoup d'importance pour les performances globales. Dans ce cas, vous pourriez être plus à l'aise avec ce que vous connaissez.

Une autre option serait d'utiliser ParallelEnumerable.Select , qui correspond à ce que vous essayez de faire de plus près. Encore une fois, toute différence de performances que vous verrez sera probablement négligeable et il n'y a rien de mal à s'en tenir à ce que vous savez.

Comme toujours, si les performances de cet élément sont essentielles, rien ne peut le remplacer et le mesurer.

22
bmm6o

Il me semble que le bmm6o n'est pas correct. L'instance ConcurrentBag contient en interne des mini-sacs pour chaque thread qui y ajoute des éléments, donc l'insertion d'élément n'implique aucun verrouillage de thread, et donc tous les threads Environment.ProcessorCount Peuvent se mettre en marche sans être coincés en attente et sans aucun changement de contexte de thread. Une sinchronisation de thread peut nécessiter lors de l'itération sur les éléments collectés, mais encore une fois dans l'exemple d'origine, l'itération est effectuée par un seul thread une fois toutes les insertions effectuées. De plus, si le ConcurrentBag utilise des techniques Interlocked comme première couche de la synchronisation des threads, il est possible de ne pas impliquer du tout les opérations Monitor.

D'un autre côté, l'utilisation d'une instance List<T> Habituelle et l'encapsulation de chacun de ses appels de méthode Add () avec un mot clé de verrouillage nuiront beaucoup aux performances. Tout d'abord, en raison des appels constants Monitor.Enter() et Monitor.Exit() qui nécessitent chacun de pénétrer profondément dans le mode noyau et de travailler avec les primitives de synchronisation Windows. Deuxièmement, parfois un thread peut parfois être bloqué par le deuxième thread car le deuxième thread n'a pas encore terminé son ajout.

Quant à moi, le code ci-dessus est un très bon exemple de la bonne utilisation de la classe ConcurrentBag.

5
Sergey Volodko

Si List<T> Est utilisé avec un verrou autour de la méthode Add(), cela fera attendre les threads et réduira le gain de performances de l'utilisation de Parallel.ForEach()

0
Adeel Meer