Quand utiliser le pool de threads en C #? [fermé]


127

J'ai essayé d'apprendre la programmation multi-thread en C # et je ne sais pas quand il est préférable d'utiliser un pool de threads plutôt que de créer mes propres threads. Un livre recommande d'utiliser un pool de threads uniquement pour les petites tâches (quoi que cela signifie), mais je n'arrive pas à trouver de véritables directives. Quelles considérations utilisez-vous lorsque vous prenez cette décision de programmation?

Réponses:


47

Si vous avez beaucoup de tâches logiques qui nécessitent un traitement constant et que vous souhaitez que cela se fasse en parallèle, utilisez le pool + planificateur.

Si vous devez effectuer vos tâches liées aux E / S simultanément, telles que le téléchargement de données à partir de serveurs distants ou l'accès au disque, mais que vous devez le faire toutes les quelques minutes, créez vos propres threads et tuez-les une fois que vous avez terminé.

Edit: À propos de certaines considérations, j'utilise des pools de threads pour l'accès à la base de données, la physique / simulation, l'IA (jeux) et pour les tâches scriptées exécutées sur des machines virtuelles qui traitent de nombreuses tâches définies par l'utilisateur.

Normalement, un pool se compose de 2 threads par processeur (donc probablement 4 de nos jours), mais vous pouvez configurer la quantité de threads que vous voulez, si vous savez combien vous en avez besoin.

Edit: La raison pour laquelle vous créez vos propres threads est à cause des changements de contexte, (c'est quand les threads ont besoin d'échanger dans et hors du processus, avec leur mémoire). Avoir des changements de contexte inutiles, par exemple lorsque vous n'utilisez pas vos threads, en les laissant simplement assis comme on pourrait le dire, peut facilement réduire la moitié des performances de votre programme (disons que vous avez 3 threads en veille et 2 threads actifs). Ainsi, si ces threads de téléchargement attendent juste, ils consomment des tonnes de CPU et refroidissent le cache pour votre application réelle.


2
Ok, mais pouvez-vous expliquer pourquoi c'est ainsi que vous l'abordez? Par exemple, quel est l'inconvénient d'utiliser le pool de threads pour télécharger à partir de serveurs distants ou effectuer des E / S sur disque?

8
Si un thread attend un objet de synchronisation (événement, sémaphore, mutex, etc.), le thread ne consomme pas de CPU.
Brannon le

7
Comme l'a dit Brannon, un mythe courant est que la création de plusieurs threads a un impact sur les performances. En fait, les threads inutilisés consomment très peu de ressources. Les commutateurs de contexte ne commencent à poser problème que sur les serveurs à très forte demande (dans ce cas, consultez les ports d'achèvement d'E / S pour une alternative).
FDCastel

12
Les threads inactifs ont-ils un impact sur les performances? Cela dépend de la façon dont ils attendent. S'ils sont bien écrits et attendent un objet de synchronisation, ils ne devraient pas consommer de ressources CPU. Si attendre dans une boucle qui se réveille périodiquement pour vérifier les résultats, cela gaspille du processeur. Comme toujours, il s'agit d'un bon codage.
Bill

2
Les threads gérés inactifs consomment de la mémoire pour leur pile. Par défaut, 1 Mio par thread. Il est donc préférable que tous les threads fonctionnent.
Vadym Stetsiak

48

Je vous suggère d'utiliser un pool de threads en C # pour les mêmes raisons que tout autre langage.

Lorsque vous souhaitez limiter le nombre de threads en cours d'exécution ou que vous ne voulez pas la surcharge de création et de destruction, utilisez un pool de threads.

Par petites tâches, le livre que vous lisez signifie des tâches à courte durée de vie. S'il faut dix secondes pour créer un thread qui ne fonctionne que pendant une seconde, c'est un endroit où vous devriez utiliser des pools (ignorez mes chiffres réels, c'est le ratio qui compte).

Sinon, vous passez le plus clair de votre temps à créer et à détruire des threads plutôt que de simplement faire le travail qu'ils sont censés faire.


28

Voici un joli résumé du pool de threads dans .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

La publication a également quelques points sur le moment où vous ne devez pas utiliser le pool de threads et démarrer votre propre thread à la place.


8
-1 pour le lien. Je suis sûr que c'est un bon lien, mais je m'attends à ce que SO soit autosuffisant.
Jon Davis

26
@ stimpy77 - c'est alors la mauvaise attente. L'OS ne peut jamais être autosuffisant, car ce n'est ni l'autorité ultime sur toutes les questions, ni toutes les informations approfondies sur chaque sujet peuvent (et devraient) être dupliquées dans chaque réponse d'OS qui touche ce sujet. (et je ne pense pas que vous ayez même assez de réputation pour voter contre chaque réponse de Jon Skeet seule qui a un lien sortant, sans parler de toutes les réponses de tous les utilisateurs SO qui ont des liens sortants :-))
Franci Penov

2
J'étais peut-être trop succinct, peut-être devrais-je clarifier. Je ne suis pas contre les liens. Je suis contre les réponses qui ne contiennent qu'un lien. Je ne pense pas que ce soit une réponse. Maintenant, si un bref résumé de la réponse avait été posté pour résumer la façon dont le contenu lié s'applique, ce serait acceptable. D'ailleurs, je suis venu ici à la recherche d'une réponse au même problème et cette réponse m'a énervé car c'était encore un autre lien sur lequel je devais cliquer pour avoir une idée de ce que cela pourrait dire en référence au problème spécifique. Quoi qu'il en soit, où est lié Jon Skeet à cela? Et pourquoi devrais-je m'en soucier?
Jon Davis

8
"Vous êtes arrivé à ce poste deux ans après sa publication et tout ce que j'ai copié ici était peut-être obsolète maintenant." Ainsi pourrait un lien. Publiez un résumé succinct mais complet lors de la publication d'un lien, vous ne savez jamais si un lien est périmé ou mort.
Jon Davis

2
Je ne suis pas d'accord avec stimpy: pas l'idée de messages contenant des tonnes d'informations en raison de l'infaisabilité, ni d'appeler quelqu'un à ce sujet. Je dirais qu'il est plus probable qu'un lien devienne inopérant que le contenu devienne obsolète / évité, cependant. Donc, plus de contenu est agréable lorsque l'occasion le permet. Nous sommes tous (pour la plupart) des bénévoles, alors soyez reconnaissants pour ce que vous obtenez - merci Franci :)
zanlok

14

Je recommande vivement de lire ce livre électronique gratuit: Threading in C # par Joseph Albahari

Au moins, lisez la section «Premiers pas». Le livre électronique fournit une excellente introduction et comprend également une mine d'informations avancées sur les fils.

Savoir s'il faut utiliser ou non le pool de threads n'est qu'un début. Ensuite, vous devrez déterminer quelle méthode d'entrée dans le pool de threads correspond le mieux à vos besoins:

  • Bibliothèque parallèle de tâches (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Délégués asynchrones
  • Arrière-plan

Cet e-book explique tout cela et vous conseille quand les utiliser ou créer votre propre fil.


8

Le pool de threads est conçu pour réduire le changement de contexte entre vos threads. Considérez un processus qui a plusieurs composants en cours d'exécution. Chacun de ces composants peut créer des threads de travail. Plus votre processus comporte de threads, plus vous perdez de temps à changer de contexte.

Désormais, si chacun de ces composants mettait des éléments en file d'attente dans le pool de threads, vous auriez beaucoup moins de surcharge de changement de contexte.

Le pool de threads est conçu pour maximiser le travail effectué sur vos processeurs (ou cœurs de processeur). C'est pourquoi, par défaut, le pool de threads fait tourner plusieurs threads par processeur.

Il existe certaines situations dans lesquelles vous ne souhaitez pas utiliser le pool de threads. Si vous attendez des E / S, ou attendez un événement, etc., vous attachez ce thread de pool de threads et il ne peut être utilisé par personne d'autre. La même idée s'applique aux tâches de longue durée, bien que ce qui constitue une tâche de longue durée soit subjectif.

Pax Diablo fait également un bon point. Faire tourner des fils n'est pas gratuit. Cela prend du temps et ils consomment de la mémoire supplémentaire pour leur espace de pile. Le pool de threads réutilisera les threads pour amortir ce coût.

Remarque: vous avez demandé comment utiliser un thread de pool de threads pour télécharger des données ou effectuer des E / S de disque. Vous ne devez pas utiliser un thread de pool de threads pour cela (pour les raisons que j'ai décrites ci-dessus). Utilisez plutôt les E / S asynchrones (également appelées méthodes BeginXX et EndXX). Pour un FileStreamce serait BeginReadet EndRead. Pour un HttpWebRequestce serait BeginGetResponseet EndGetResponse. Ils sont plus compliqués à utiliser, mais ils constituent le bon moyen d'effectuer des E / S multithreads.


1
ThreadPool est un automatisme intelligent. "Si sa file d'attente reste stationnaire pendant plus d'une demi-seconde, il répond en créant plus de threads - un toutes les demi-secondes - jusqu'à la capacité du pool de threads" ( albahari.com/threading/#_Optimizing_the_Thread_Pool ). Des opérations presque asynchrones avec BeginXXX-EndXXX sont également utilisées via ThreadPool. Il est donc normal d'utiliser ThreadPool pour télécharger des données et souvent utilisé implicitement.
Artru

6

Méfiez-vous du pool de threads .NET pour les opérations qui peuvent bloquer pour toute partie significative, variable ou inconnue de leur traitement, car il est sujet à la famine des threads. Envisagez d'utiliser les extensions parallèles .NET, qui fournissent un bon nombre d'abstractions logiques sur les opérations threadées. Ils incluent également un nouveau planificateur, qui devrait être une amélioration de ThreadPool. Voir ici


2
Nous avons découvert cela à la dure! ASP.Net utilise le Threadpool qui apparaît et nous ne pouvons donc pas l'utiliser aussi agressivement que nous le souhaiterions.
noocyte

3

Une des raisons d'utiliser le pool de threads uniquement pour les petites tâches est qu'il existe un nombre limité de threads de pool de threads. Si l'un est utilisé pendant une longue période, il empêche ce thread d'être utilisé par un autre code. Si cela se produit plusieurs fois, le pool de threads peut être épuisé.

L'utilisation du pool de threads peut avoir des effets subtils - certains minuteurs .NET utilisent des threads de pool de threads et ne se déclenchent pas, par exemple.


2

Si vous avez une tâche d'arrière-plan qui durera longtemps, comme pendant toute la durée de vie de votre application, créer votre propre thread est une chose raisonnable. Si vous avez des tâches courtes à effectuer dans un thread, utilisez le pool de threads.

Dans une application où vous créez de nombreux threads, la surcharge de création des threads devient substantielle. L'utilisation du pool de threads crée les threads une fois et les réutilise, évitant ainsi la surcharge de création de thread.

Dans une application sur laquelle j'ai travaillé, passer de la création de threads à l'utilisation du pool de threads pour les threads de courte durée a vraiment aidé à la mise en œuvre de l'application.


Veuillez préciser si vous voulez dire "un pool de threads" ou "le pool de threads". Ce sont des choses très différentes (du moins dans le MS CLR).
bzlm

2

Pour des performances optimales avec des unités exécutées simultanément, écrivez votre propre pool de threads, où un pool d'objets Thread est créé au démarrage et passe au blocage (anciennement suspendu), en attendant un contexte à exécuter (un objet avec une interface standard implémentée par votre code).

Tant d'articles sur Tâches vs Threads vs .NET ThreadPool ne vous donnent pas vraiment ce dont vous avez besoin pour prendre une décision en matière de performances. Mais quand vous les comparez, les Threads l'emportent et surtout un pool de Threads. Ils sont les meilleurs répartis sur les processeurs et démarrent plus rapidement.

Ce qui devrait être discuté est le fait que l'unité d'exécution principale de Windows (y compris Windows 10) est un thread et que la surcharge de changement de contexte du système d'exploitation est généralement négligeable. En termes simples, je n'ai pas été en mesure de trouver des preuves convaincantes de bon nombre de ces articles, que l'article revendique de meilleures performances en économisant le changement de contexte ou une meilleure utilisation du processeur.

Maintenant, pour un peu de réalisme:

La plupart d'entre nous n'auront pas besoin que notre application soit déterministe, et la plupart d'entre nous n'ont pas une expérience difficile avec les threads, ce qui, par exemple, vient souvent avec le développement d'un système d'exploitation. Ce que j'ai écrit ci-dessus n'est pas pour un débutant.

Le plus important est donc de discuter de ce qui est facile à programmer.

Si vous créez votre propre pool de threads, vous aurez un peu d'écriture à faire car vous devrez vous préoccuper du suivi de l'état d'exécution, comment simuler la suspension et la reprise, et comment annuler l'exécution - y compris dans une application à l'échelle fermer. Vous devrez peut-être également vous demander si vous souhaitez développer dynamiquement votre pool et quelle sera la limitation de capacité de votre pool. Je peux écrire un tel cadre en une heure, mais c'est parce que je l'ai fait tant de fois.

Le moyen le plus simple d'écrire une unité d'exécution est peut-être d'utiliser une tâche. La beauté d'une tâche est que vous pouvez en créer une et la lancer en ligne dans votre code (bien que la prudence puisse être justifiée). Vous pouvez transmettre un jeton d'annulation à gérer lorsque vous souhaitez annuler la tâche. En outre, il utilise l'approche de promesse pour chaîner les événements et vous pouvez lui faire renvoyer un type de valeur spécifique. De plus, avec async et await, plus d'options existent et votre code sera plus portable.

En substance, il est important de comprendre les avantages et les inconvénients de Tasks vs Threads vs .NET ThreadPool. Si j'ai besoin de hautes performances, je vais utiliser des threads et je préfère utiliser mon propre pool.

Un moyen simple de comparer est de démarrer 512 threads, 512 tâches et 512 threads ThreadPool. Vous trouverez un retard au début avec les threads (d'où pourquoi écrire un pool de threads), mais les 512 threads s'exécuteront dans quelques secondes tandis que les threads Tasks et .NET ThreadPool prennent jusqu'à quelques minutes pour démarrer.

Voici les résultats d'un tel test (i5 quad core avec 16 Go de RAM), donnant toutes les 30 secondes pour fonctionner. Le code exécuté effectue des E / S de fichier simples sur un lecteur SSD.

Résultats de test


1
Pour info, j'ai oublié de mentionner que les tâches et les threads .NET sont une simultanéité simulée dans .NET et que la gestion s'exécute dans .NET et non dans le système d'exploitation - ce dernier étant beaucoup plus efficace pour gérer les exécutions simultanées. J'utilise les tâches pour beaucoup de choses mais j'utilise un thread OS pour des performances d'exécution élevées. MS affirme que les tâches et les threads .NET sont meilleurs, mais ils servent en général à équilibrer la concurrence entre les applications .NET. Cependant, une application serveur fonctionnerait mieux en laissant le système d'exploitation gérer la concurrence.

J'adorerais voir l'implémentation de votre Threadpool personnalisé. Belle rédaction!
Francis

Je ne comprends pas vos résultats de test. Que signifie "Unités Ran"? Vous comparez 34 taks avec 512 threads? Pouvez-vous expliquer cela?
Elmue

Unit est juste une méthode à exécuter simultanément dans un thread de travail Task, Thread ou .NET ThreadPool, mon test comparant les performances de démarrage / exécution. Chaque test dispose de 30 secondes pour générer 512 threads à partir de zéro, 512 tâches, 512 threads de travail ThreadPool ou pour reprendre un pool de 512 threads démarrés en attente d'un contexte à exécuter. Les threads de travail Tasks et ThreadPool ont une rotation lente, donc 30 secondes ne sont pas assez de temps pour tous les faire tourner. Toutefois, si le nombre de threads de travail ThreadPool min est d'abord défini sur 512, les threads de travail Tâches et ThreadPool tournent presque aussi vite que 512 threads à partir de zéro.


1

Les pools de threads sont parfaits lorsque vous avez plus de tâches à traiter que de threads disponibles.

Vous pouvez ajouter toutes les tâches à un pool de threads et spécifier le nombre maximum de threads pouvant s'exécuter à un moment donné.

Consultez cette page sur MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx


Ok, je suppose que cela rejoint mon autre question. Comment savez-vous combien de threads disponibles vous avez à un moment donné?

Eh bien, c'est difficile à dire. Vous devrez faire des tests de performances. Après un certain temps, ajouter plus de threads ne vous donnera pas plus de vitesse. Découvrez le nombre de processeurs sur la machine, ce sera un bon point de départ. Ensuite, montez à partir de là, si la vitesse de traitement ne s'améliore pas, n'ajoutez pas plus de threads.
lajos

1

Utilisez toujours un pool de threads si vous le pouvez, travaillez au plus haut niveau d'abstraction possible. Les pools de threads cachent la création et la destruction de threads pour vous, c'est généralement une bonne chose!


1

La plupart du temps, vous pouvez utiliser le pool car vous évitez le processus coûteux de création du thread.

Toutefois, dans certains scénarios, vous souhaiterez peut-être créer un thread. Par exemple, si vous n'êtes pas le seul à utiliser le pool de threads et que le thread que vous créez est de longue durée (pour éviter de consommer des ressources partagées) ou par exemple si vous souhaitez contrôler la stacksize du thread.


1

N'oubliez pas d'enquêter sur l'agent d'arrière-plan.

Je trouve que pour beaucoup de situations, cela me donne exactement ce que je veux sans le gros du travail.

À votre santé.


quand c'est une application simple qui reste en cours d'exécution et que vous avez une autre tâche à faire, il est très facile de faire ce code. vous n'avez pas fourni de liens cependant: spécification et tutoriel
zanlok

0

J'utilise généralement le Threadpool chaque fois que j'ai besoin de faire quelque chose sur un autre thread et que je ne me soucie pas vraiment de son exécution ou de sa fin. Quelque chose comme la journalisation ou peut-être même le téléchargement d'un fichier en arrière-plan (bien qu'il existe de meilleures façons de le faire de manière asynchrone). J'utilise mon propre thread lorsque j'ai besoin de plus de contrôle. De plus, ce que j'ai trouvé, c'est d'utiliser une file d'attente Threadsafe (pirater la vôtre) pour stocker des "objets de commande", c'est bien quand j'ai plusieurs commandes sur lesquelles je dois travailler dans> 1 thread. Ainsi, vous pouvez diviser un fichier Xml et mettre chaque élément dans une file d'attente, puis plusieurs threads travaillent à effectuer un traitement sur ces éléments. J'ai écrit une telle file d'attente en uni (VB.net!) Que j'ai convertie en C #. Je l'ai inclus ci-dessous sans raison particulière (ce code peut contenir des erreurs).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        public void EnqueueSafe(T item) {
            lock ( this ) {
                _queue.Enqueue(item);
                if ( _queue.Count >= 1 )
                    Monitor.Pulse(this);
            }
        }

        public T DequeueSafe() {
            lock ( this ) {
                while ( _queue.Count <= 0 )
                    Monitor.Wait(this);

                return this.DeEnqueueUnblock();

            }
        }

        private T DeEnqueueUnblock() {
            return _queue.Dequeue();
        }
    }
}

Quelques problèmes avec cette approche: - Les appels à DequeueSafe () attendront qu'un élément soit EnqueuedSafe (). Envisagez d'utiliser l'une des surcharges Monitor.Wait () en spécifiant un délai d'expiration. - Le verrouillage n'est pas conforme aux meilleures pratiques, créez plutôt un champ d'objet en lecture seule. - Même si Monitor.Pulse () est léger, l'appeler lorsque la file d'attente ne contient qu'un seul élément serait plus efficace. - DeEnqueueUnblock () devrait de préférence vérifier la queue.Count> 0. (nécessaire si Monitor.PulseAll ou les délais d'attente sont utilisés)
Craig Nicholson

0

Je voulais un pool de threads pour distribuer le travail entre les cœurs avec le moins de latence possible, et cela ne devait pas bien fonctionner avec d'autres applications. J'ai trouvé que les performances du pool de threads .NET n'étaient pas aussi bonnes qu'elles pourraient l'être. Je savais que je voulais un thread par cœur, j'ai donc écrit ma propre classe de remplacement de pool de threads. Le code est fourni en tant que réponse à une autre question StackOverflow ici .

Quant à la question initiale, le pool de threads est utile pour diviser les calculs répétitifs en parties qui peuvent être exécutées en parallèle (en supposant qu'ils peuvent être exécutés en parallèle sans changer le résultat). La gestion manuelle des threads est utile pour des tâches telles que l'interface utilisateur et IO.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.