Collections simultanées
1 Pourquoi utiliser des collections simultanées ?
Les principales raisons sont les suivantes :
Les listes, collections et tableaux classiques fournis dans les espaces de noms System.Collections et System.Collections.Generic ne sont pas thread-safe Sans mécanisme de synchronisation, ils ne conviennent pas pour accepter des instructions concurrentes pour ajouter et supprimer des éléments.
L'utilisation des collections classiques ci-dessus en code concurrent nécessite une gestion de synchronisation complexe, ce qui est très peu pratique à utiliser.
L'utilisation de mécanismes de synchronisation complexes réduira considérablement les performances.
Les nouvelles collections fournies par NET Framework 4 minimisent le nombre de fois où vous devez utiliser les verrous. Ces nouvelles collections évitent l'utilisation de verrous lourds mutuellement exclusifs en utilisant des instructions de comparaison et d'échange (CAS) et des barrières de mémoire. Cela garantit les performances.
Remarque : Par rapport aux collections classiques, les collections simultanées auront une surcharge plus importante, donc l'utilisation de collections simultanées dans le code série n'a aucun sens et ne fera qu'augmenter la surcharge supplémentaire et la vitesse d'exécution. collection.
2 collections simultanées
1) ConcurrentQueue : collection FIFO (premier entré, premier sorti) thread-safe
Méthodes principales :
Mettre en file d'attente (élément T); Ajoute l'objet à la fin de la collection.
TryDequeue(out T result); Essayez de supprimer et de renvoyer l'objet au début de la collection. La valeur de retour indique si l'opération a réussi.
TryPeek(out T result); Essayez de renvoyer l'objet au début de la collection sans le supprimer. La valeur de retour indique si l'opération a réussi.
Remarque :
ConcurrentQueue est totalement sans verrouillage, mais lorsqu'une opération CAS échoue et fait face à un conflit de ressources, elle peut tourner et réessayer. opération.
ConcurrentQueue est une collection FIFO. Dans certaines situations qui n'ont rien à voir avec l'ordre d'entrée et de sortie, essayez de ne pas utiliser ConcurrentQueue.
2) ConcurrentStack : collection dernier entré, premier sorti (LIFO) thread-safe
Principaux méthodes et attributs :
Push (élément T);Insère l'objet en haut de la collection.
TryPop(out T result); Essayez de faire apparaître et renvoyer l'objet en haut de la collection. La valeur de retour indique si l'opération a réussi.
TryPeek(out T result); Essayez de renvoyer l'objet au début de la collection sans le supprimer. La valeur de retour indique si l'opération a réussi.
IsEmpty { get; Indique si la collection est vide.
PushRange(T[] items); Insère plusieurs objets en haut de la collection.
TryPopRange(T[] items); fait apparaître plusieurs éléments en haut, et le résultat renvoyé est le nombre d'éléments sautés.
Remarque :
Semblable à ConcurrentQueue, ConcurrentStack est totalement sans verrouillage, mais lorsqu'une opération CAS échoue et fait face à un conflit de ressources, elle peut Va tourner et réessayer l'opération.
Pour savoir si la collection contient des éléments, utilisez la propriété IsEmpty au lieu de juger si la propriété Count est supérieure à zéro. Appeler Count coûte plus cher que d’appeler IsEmpty.
Soyez conscient de la surcharge supplémentaire et de la consommation de mémoire supplémentaire causées par la mise en mémoire tampon lors de l'utilisation de PushRange (éléments T[]) et TryPopRange (éléments T[]).
3) ConcurrentBag : une collection non ordonnée avec des éléments répétables
Principales méthodes et attributs :
TryPeek(out T result ); tente de renvoyer un objet de la collection sans supprimer l'objet. La valeur de retour indique si l'objet a été obtenu avec succès.
TryTake(out T result); Essayez de renvoyer un objet de la collection et supprimez l'objet. La valeur de retour indique si l'objet a été obtenu avec succès.
Add(T item); Ajouter l'objet à la collection.
IsEmpty { get; } L'explication est la même que ConcurrentStack
Description :
ConcurrentBag pour chaque Le thread accédant à la collection maintient une file d'attente locale et, lorsque cela est possible, accède à la file d'attente locale sans verrouillage.
ConcurrentBag est très efficace lors de l'ajout et de la suppression d'éléments dans le même fil de discussion.
Étant donné que ConcurrentBag nécessite parfois des verrous, il est très inefficace dans les scénarios où les threads producteurs et les threads consommateurs sont complètement séparés.
L'appel de ConcurrentBag à IsEmpty est très coûteux car il nécessite d'acquérir temporairement tous les verrous de ce groupe non ordonné.
4) BlockingCollection : une collection thread-safe qui implémente
System.Collections.Concurrent.IProducerConsumerCollection
Principalement méthodes et propriétés :
BlockingCollection(int bornedCapacity bornedCapacity représente la taille limite de la collection);
CompleteAdding(); Marque l'instance BlockingCollection comme n'acceptant plus aucun ajout.
IsCompleted { get; } Indique si cette collection a été marquée comme terminée et vide.
GetConsumingEnumerable(); Supprime et renvoie l'élément supprimé de la collection
Add(T item);
TryTake(T item, int millisecondsTimeout, CancellationToken CancellationToken);
Instructions :
Utiliser BlockingCollection( ) le constructeur instancie BlockingCollection, ce qui signifie que bornedCapacity n'est pas défini, alors bornedCapacity est la valeur par défaut : int.MaxValue.
Bounded : utilisez BlockingCollection(intboundedCapacity) pour définir la valeur de bornedCapacity Lorsque la capacité de collecte atteint cette valeur, le thread ajoutant des éléments à BlockingCollection sera bloqué jusqu'à ce qu'un élément soit supprimé. .
La fonction de délimitation contrôle la taille maximale de la collection en mémoire, ce qui est très utile lorsqu'un grand nombre d'éléments doivent être traités.
Par défaut, BlockingCollection encapsule une ConcurrentQueue. Vous pouvez spécifier une collection simultanée qui implémente l'interface IProducerConsumerCollection dans le constructeur, notamment : ConcurrentStack et ConcurrentBag.
L'utilisation de cette collection implique le risque d'attendre indéfiniment, il est donc plus pratique d'utiliser TryTake, car TryTake fournit un contrôle de délai d'attente et un élément peut être supprimé de la collection dans un délai spécifié. . est vrai ; sinon, c’est faux.
5) ConcurrentDictionary : une collection thread-safe de paires clé-valeur accessibles simultanément par plusieurs threads.
Méthode principale
AddOrUpdate(TKey key, TValue addValue, Func
GetOrAdd(TKey key, TValue value); Ajoute une paire clé/valeur au dictionnaire si la clé spécifiée n'existe pas déjà.
TryRemove(TKey key, out TValue value);Essayez de supprimer du dictionnaire et renvoyez la valeur avec la clé spécifiée.
TryUpdate(TKey key, TValue newValue, TValue comparativeValue); Compare la valeur existante de la clé spécifiée avec la valeur spécifiée, et si elle est égale, met à jour la clé avec la troisième valeur);
Remarque :
ConcurrentDictionary est totalement sans verrouillage pour les opérations de lecture. ConcurrentDictionary utilise des verrous précis lorsque plusieurs tâches ou threads y ajoutent des éléments ou y modifient des données. L’utilisation de verrous fins ne verrouille que la partie qui doit réellement être verrouillée, et non l’intégralité du dictionnaire.
6) IProducerConsumerCollection : définit des méthodes permettant aux producteurs/consommateurs d'exploiter des collections thread-safe. Cette interface fournit une représentation unifiée (pour les collections producteur/consommateur) afin que les abstractions de niveau supérieur telles que System.Collections.Concurrent.BlockingCollection
3. Modèles communs
1) Modèle parallèle producteur-consommateur
Définition :
Le producteur et le consommateur sont dans ce modèle Deux types de modèles d'objet , le consommateur dépend des résultats du producteur, tandis que le producteur génère les résultats, le consommateur utilise les résultats.
Figure 1 Modèle parallèle producteur-consommateur
Explication :
La collection simultanée est utilisée ici, le modèle est un bon ajustement car les collections simultanées prennent en charge les opérations parallèles sur les objets de ce modèle.
Si vous n'utilisez pas de collections simultanées, vous devez ajouter un mécanisme de synchronisation, ce qui rend le programme plus complexe, difficile à maintenir et à comprendre, et réduit considérablement les performances.
L'image ci-dessus est un diagramme schématique du modèle producteur-consommateur. L'axe vertical est la chronologie. Le producteur et le consommateur ne sont pas sur la même chronologie, mais ils se chevauchent. est destiné à montrer que le générateur produit d'abord des résultats, puis le consommateur utilise réellement les données générées par le générateur.
2) Modèle de pipeline
Définition :
Un pipeline se compose de plusieurs étapes, chaque étape se compose d'une série de producteurs et de consommateurs. D'une manière générale, l'étape précédente est le générateur de l'étape ultérieure ; s'appuyant sur la file d'attente tampon entre deux étapes adjacentes, chaque étape peut être exécutée simultanément.
Figure 2 Mode pipeline parallèle
Explication :
BlockingCollection
La vitesse du pipeline est approximativement égale à la vitesse de l'étage le plus lent du pipeline.
L'image ci-dessus est un diagramme schématique du mode pipeline. L'étape précédente est le générateur de l'étape ultérieure. Le mode pipeline le plus simple et le plus basique peut être montré ici. considéré comme chaque étape comprend davantage de traitement des données.
4 Utilisation
Seuls ConcurrentBag et BlockingCollection sont pris comme exemples, d'autres collections simultanées sont similaires.
ConcurrentBag
List<string> list = ...... ConcurrentBag<string> bags = new ConcurrentBag<string>(); Parallel.ForEach(list, (item) => { //对list中的每个元素进行处理然后,加入bags中 bags.Add(itemAfter); });
BlockingCollection – Modèle de consommateur producteur
public static void Execute() { //调用Invoke,使得生产者任务和消费者任务并行执行 //Producer方法和Customer方法在Invoke中的参数顺序任意,不论何种顺序都会获得正确的结果 Parallel.Invoke(()=>Customer(),()=>Producer()); Console.WriteLine(string.Join(",",customerColl)); } //生产者集合 private static BlockingCollection<int> producerColl = new BlockingCollection<int>(); //消费者集合 private static BlockingCollection<string> customerColl = new BlockingCollection<string>(); public static void Producer() { //循环将数据加入生成者集合 for (int i = 0; i < 100; i++) { producerColl.Add(i); } //设置信号,表明不在向生产者集合中加入新数据 //可以设置更加复杂的通知形式,比如数据量达到一定值且其中的数据满足某一条件时就设置完成添加 producerColl.CompleteAdding(); } public static void Customer() { //调用IsCompleted方法,判断生产者集合是否在添加数据,是否还有未"消费"的数据 //注意不要使用IsAddingCompleted,IsAddingCompleted只表明集合标记为已完成添加,而不能说明其为空 //而IsCompleted为ture时,那么IsAddingCompleted为ture且集合为空 while (!producerColl.IsCompleted) { //调用Take或TryTake "消费"数据,消费一个,移除一个 //TryAdd的好处是提供超时机制 customerColl.Add(string.Format("消费:{0}", producerColl.Take())); } }
Ce qui précède est le contenu de la programmation multithread .NET - collection simultanée Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !