web-dev-qa-db-fra.com

Quand utiliser le pool de threads en C #?

J'ai essayé d'apprendre la programmation multi-thread en C # et je ne sais pas trop 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 (peu importe ce que cela signifie), mais je ne semble pas pouvoir trouver de véritable guide. Quelles sont les considérations que vous utilisez lorsque vous prenez cette décision de programmation?

125

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

Si vous devez effectuer simultanément vos tâches connexes IO telles que le téléchargement de fichiers à partir de serveurs distants ou l'accès au disque), vous devez le faire toutes les quelques minutes, puis créez vos propres threads et supprimez-les une fois. tu as fini.

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 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 est constitué de 2 threads par processeur (donc probablement de 4 aujourd'hui), mais vous pouvez définir le nombre de threads souhaité, si vous en connaissez le nombre.

Edit: La raison pour laquelle vous créez vos propres threads est due aux changements de contexte (c'est-à-dire lorsque les threads doivent basculer dans et hors du processus, avec leur mémoire). Avoir des changements de contexte inutiles, disons que lorsque vous n'utilisez pas vos threads, les laisser reposer comme on pourrait le dire peut facilement réduire de moitié les performances de votre programme (par exemple, vous avez 3 threads en veille et 2 en activité). Ainsi, si ces threads de téléchargement n'attendent que vous, ils consomment des tonnes de CPU et refroidissent le cache de votre application réelle.

46
Robert Gould

Je suggérerais que vous utilisiez un pool de threads en C # pour les mêmes raisons que tout autre langage.

Si vous souhaitez limiter le nombre de threads en cours d'exécution ou si vous ne souhaitez pas que leur charge de travail soit créée ou détruite, utilisez un pool de threads.

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

Sinon, vous passerez 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.

48
paxdiablo

Voici un bon 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 contient également des informations sur le moment où vous ne devez pas utiliser le pool de threads et démarrez votre propre thread à la place.

28
Franci Penov

Je recommande fortement de lire cet e-book gratuit: Threading in C # de Joseph Albahari

Au moins lisez la section "Mise en route". Le livre électronique constitue une excellente introduction et comprend également une mine d'informations avancées sur le filetage.

Savoir s'il faut ou non utiliser le pool de threads n'est que le 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
  • BackgroundWorker

Cet e-book explique tout cela et conseille quand les utiliser vs créer votre propre sujet.

14
jrupe

Le pool de threads est conçu pour réduire le changement de contexte entre vos threads. Considérons un processus qui a plusieurs composants en cours d'exécution. Chacun de ces composants pourrait créer des threads de travail. Plus il y a de threads dans votre processus, 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 temps de commutation de contexte.

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

Dans certaines situations, vous ne voudriez pas utiliser le pool de threads. Si vous attendez une entrée/sortie, un événement, etc., vous liez ce thread de pool de threads qui 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 une bonne remarque. Faire filer des fils n'est pas gratuit. Cela prend du temps et consomme de la mémoire supplémentaire pour leur espace de pile. Le pool de threads va réutiliser les threads pour amortir ce coût.

Remarque: vous avez demandé à utiliser un thread de pool de threads pour télécharger des données ou effectuer des E/S sur disque. Vous ne devez pas utiliser de thread de pool de threads pour cela (pour les raisons que j'ai expliquées ci-dessus). Utilisez plutôt des E/S asynchrones (les méthodes BeginXX et EndXX). Pour un FileStream, ce serait BeginRead et EndRead. Pour un HttpWebRequest, ce serait BeginGetResponse et EndGetResponse. Leur utilisation est plus compliquée, mais c’est le moyen approprié d’effectuer des E/S multithreads.

8
Brannon

Méfiez-vous du pool de threads .NET pour les opérations susceptibles de bloquer toute partie significative, variable ou inconnue de leur traitement, car il est sujet aux pannes de threads. Pensez à utiliser les extensions parallèles .NET, qui fournissent un bon nombre d'abstractions logiques sur des opérations threadées. Ils incluent également un nouvel ordonnanceur, qui devrait constituer une amélioration de ThreadPool. Voir ici

6
mancaus

Une des raisons d'utiliser le pool de threads uniquement pour de petites tâches est qu'il existe un nombre limité d'unités d'exécution. Si l'un d'entre eux est utilisé pendant longtemps, il empêche l'utilisation de ce thread par un autre code. Si cela se produit plusieurs fois, le pool de threads peut être utilisé.

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

3
Thomas Bratt

Si vous avez une tâche en arrière-plan qui durera longtemps, comme pour toute la durée de vie de votre application, créer votre propre thread est une chose raisonnable. Si des tâches courtes doivent être effectuées dans un thread, utilisez le pool de threads.

Dans une application dans laquelle vous créez plusieurs threads, la charge de création de ceux-ci devient importante. 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 éphémères empêchait vraiment le déroulement de l'application.

2
Bill

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

Tant d'articles sur les tâches, les threads et le thread .NET ThreadPool ne vous donnent pas vraiment ce dont vous avez besoin pour prendre des décisions en matière de performances. Mais lorsque vous les comparez, les threads sont gagnants et en particulier un pool de threads. Ils sont distribués de manière optimale entre les processeurs et ils 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 du changement de contexte de 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 d'un grand nombre de ces articles, que celui-ci revendique de meilleures performances en sauvegardant le changement de contexte ou en optimisant l'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 de connaissances approfondies en matière de 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.

Donc, le plus important est 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 du statut de l'exécution, de la simulation de la suspension et de la reprise, ainsi que de la manière d'annuler une exécution, y compris dans toute l'application. fermer. Vous devrez peut-être également vous demander si vous souhaitez augmenter votre pool de manière dynamique, ainsi que les limites de capacité de votre pool. Je peux écrire un tel cadre en une heure, mais c’est parce que je l’ai déjà fait tellement 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 (même si une prudence peut être justifiée). Vous pouvez passer un jeton d'annulation à gérer lorsque vous souhaitez annuler la tâche. En outre, il utilise l'approche de promesse pour enchaîner les événements, et vous pouvez lui renvoyer un type spécifique de valeur. De plus, avec async et wait, 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 facile de comparer est de démarrer 512 threads, 512 tâches et 512 threads ThreadPool. Vous constaterez un retard au début avec Threads (d'où la nécessité d'écrire un pool de threads), mais tous les 512 Threads seront exécutés dans quelques secondes, tandis que les threads Tâches et .NET ThreadPool dureront quelques minutes.

Vous trouverez ci-dessous les résultats d’un tel test (quadricœur i5 avec 16 Go de RAM), d’une durée de 30 secondes. Le code exécuté effectue des E/S de fichier simples sur un lecteur SSD.

Résultats du test

2
user2769898

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

Cependant, dans certains scénarios, vous pouvez créer un fil de discussion. Par exemple, si vous n'êtes pas le seul à utiliser le pool de threads et que le thread que vous créez a une longue durée de vie (pour éviter de consommer des ressources partagées) ou par exemple si vous souhaitez contrôler la taille de pile du thread.

1
antonio

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 maximal de threads pouvant s'exécuter à un moment donné.

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

1
lajos

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
JeffFoster

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 me soulever.

À votre santé.

1
SetiSeeker

Je souhaitais qu'un pool de threads distribue le travail sur des cœurs avec le moins de temps de latence possible, sans avoir à jouer avec d'autres applications. J'ai constaté que les performances du pool de threads .NET n'étaient pas aussi bonnes que possible. Je savais que je voulais un thread par cœur. J'ai donc écrit ma propre classe de substitution de pool de threads. Le code est fourni en réponse à une autre question StackOverflow ici .

En ce qui concerne la question initiale, le pool de threads est utile pour décomposer les calculs répétitifs en plusieurs parties pouvant être exécutées en parallèle (en supposant qu'elles puissent être exécutées en parallèle sans modifier le résultat). La gestion manuelle des threads est utile pour des tâches telles que l'interface utilisateur et les E/S.

0
cdiggins

J'utilise habituellement Threadpool chaque fois que j'ai besoin de faire quelque chose sur un autre thread et que cela ne me dérange pas vraiment quand il tourne ou se termine. Quelque chose comme la journalisation ou peut-être même le téléchargement en arrière-plan d'un fichier (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é 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. Donc, vous pouvez diviser un fichier XML et mettre chaque élément dans une file d'attente, puis plusieurs threads travaillent sur le traitement de ces éléments. J'ai écrit une telle file d'attente dans VN.net (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();
        }
    }
}
0
noocyte