Question

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 ou 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?

Était-ce utile?

La solution

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

Si vous devez effectuer simultanément vos tâches liées aux E / S, 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 que vous avez terminé.

Édition: à 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 (il est probable que 4 aujourd'hui), mais vous pouvez définir le nombre de threads souhaité, si vous en connaissez le nombre.

Éditer: 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 le processus et en sortir, ainsi que 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 (supposons que vous ayez 3 threads en veille et 2 en cours). 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

Autres conseils

Je vous suggérerais d'utiliser un pool de threads en C # pour les mêmes raisons que pour 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 désigne des tâches dont la durée de vie est courte. S'il faut dix secondes pour créer un fil de discussion ne fonctionnant que pendant une seconde, vous devez 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.

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

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

Au moins lisez le "Mise en route". section. Le livre électronique est une excellente introduction et comprend une mine d'informations avancées sur les threads.

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 indique quand les utiliser, par opposition à créer votre propre sujet.

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.

Maintenant, 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 CPU (ou cœurs de CPU). 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 et celui-ci 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 ils consomment de la mémoire supplémentaire pour leur espace de pile. Le pool de threads réutilisera des 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.

Méfiez-vous du pool de threads .NET pour les opérations pouvant bloquer toute partie significative, variable ou inconnue de leur traitement, car il est sujet à la famine. 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

Une des raisons d'utiliser le pool de threads uniquement pour de petites tâches est qu'il existe un nombre limité de threads de pool de threads. 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.

Si vous avez une tâche d’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 vous avez des tâches courtes à exécuter dans un thread, utilisez le pool de threads.

Dans une application dans laquelle vous créez de nombreux threads, la charge de travail liée à la création des threads devient importante. Utiliser le 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é, le passage 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.

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 définition standard). interface implémentée par votre code).

Tant d'articles sur les tâches, les threads et le ThreadPool .NET 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 de bon nombre de ces articles, que ceux-ci prétendent être plus performants en sauvegardant le changement de contexte ou en utilisant mieux le processeur.

Maintenant, 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, et de la manière d'annuler l'exécution, y compris dans un environnement virtuel. fermeture de l'application dans son ensemble. 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 fait tant de fois.

La méthode la plus simple pour écrire une unité d'exécution consiste à 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 des tâches, des threads et du ThreadPool .NET. Si j'ai besoin de hautes performances, je vais utiliser des threads et je préfère utiliser mon propre pool.

Vous pouvez facilement comparer 512 threads, 512 tâches et 512 threads ThreadPool au démarrage. Vous constaterez un délai au début avec les threads (d'où la nécessité d'écrire un pool de threads), mais tous les 512 threads s'exécuteront dans quelques secondes tandis que les threads Tâches et .NET ThreadPool prendront quelques minutes pour démarrer.

Vous trouverez ci-dessous les résultats d’un tel test (quad core 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

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 cette page sur MSDN. : http://msdn.microsoft.com/en-us /library/3dasc8as(VS.80).aspx

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!

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

Toutefois, 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 la pile du thread.

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 avoir à supporter de gros efforts.

A bientôt.

J'utilise habituellement Threadpool chaque fois que je dois faire quelque chose sur un autre thread et que cela ne me dérange pas du tout de l'exécuter ou de le terminer. 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é, c’est l’utilisation d’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 j'ai besoin de 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();
        }
    }
}

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 correctement 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 fractionner des calculs répétitifs en plusieurs parties pouvant être exécutées en parallèle (en supposant qu'ils puissent être exécutés 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.

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