Pools de threads

Un pool de threads est une collection de threads de travail qui exécutent efficacement des rappels asynchrones pour le compte de l’application. Le pool de threads est principalement utilisé pour réduire le nombre de threads d’application et assurer la gestion des threads de travail. Les applications peuvent mettre en file d’attente des éléments de travail, associer le travail à des handles d’attente, mettre automatiquement en file d’attente en fonction d’un minuteur et établir une liaison avec des E/S.

Architecture du pool de threads

Les applications suivantes peuvent tirer parti de l’utilisation d’un pool de threads :

  • Application hautement parallèle et capable de distribuer un grand nombre de petits éléments de travail de façon asynchrone (par exemple, la recherche d’index distribué ou les E/S réseau).
  • Application qui crée et détruit un grand nombre de threads qui s’exécutent chacun pendant une courte période. L’utilisation du pool de threads peut réduire la complexité de la gestion des threads et la surcharge impliquée dans la création et la destruction des threads.
  • Application qui traite des éléments de travail indépendants en arrière-plan et en parallèle (par exemple, le chargement de plusieurs onglets).
  • Application qui doit effectuer une attente exclusive sur les objets du noyau ou bloquer les événements entrants sur un objet. L’utilisation du pool de threads peut réduire la complexité de la gestion des threads et augmenter les performances en réduisant le nombre de commutateurs de contexte.
  • Application qui crée des threads de serveur personnalisés pour attendre des événements.

Le pool de threads d’origine a été complètement réarchitecté dans Windows Vista. Le nouveau pool de threads est amélioré, car il fournit un type de thread de travail unique (prend en charge les E/S et les non-E/S), n’utilise pas de thread de minuteur, fournit une file d’attente de minuteur unique et fournit un thread persistant dédié. Il fournit également des groupes propre, des performances plus élevées, plusieurs pools par processus qui sont planifiés indépendamment et une nouvelle API de pool de threads.

L’architecture du pool de threads se compose des éléments suivants :

  • Threads de travail qui exécutent les fonctions de rappel
  • Threads de serveur qui attendent plusieurs handles d’attente
  • Une file d’attente de travail
  • Un pool de threads par défaut pour chaque processus
  • Fabrique de travail qui gère les threads de travail

Bonnes pratiques

La nouvelle API de pool de threads offre plus de flexibilité et de contrôle que l’API de pool de threads d’origine. Toutefois, il existe quelques différences subtiles mais importantes. Dans l’API d’origine, la réinitialisation d’attente était automatique ; dans la nouvelle API, l’attente doit être réinitialisée explicitement à chaque fois. L’API d’origine gérait automatiquement l’emprunt d’identité, transférant le contexte de sécurité du processus appelant vers le thread. Avec la nouvelle API, l’application doit définir explicitement le contexte de sécurité.

Voici les meilleures pratiques lors de l’utilisation d’un pool de threads :

  • Les threads d’un processus partagent le pool de threads. Un thread de travail unique peut exécuter plusieurs fonctions de rappel, une par une. Ces threads de travail sont gérés par le pool de threads. Par conséquent, n’arrêtez pas un thread du pool de threads en appelant TerminateThread sur le thread ou en appelant ExitThread à partir d’une fonction de rappel.

  • Une demande d’E/S peut s’exécuter sur n’importe quel thread du pool de threads. L’annulation des E/S sur un thread de pool de threads nécessite une synchronisation, car la fonction cancel peut s’exécuter sur un thread différent de celui qui gère la demande d’E/S, ce qui peut entraîner l’annulation d’une opération inconnue. Pour éviter cela, fournissez toujours la structure OVERLAPPED avec laquelle une demande d’E/S a été lancée lors de l’appel de CancelIoEx pour des E/S asynchrones, ou utilisez votre propre synchronisation pour vous assurer qu’aucune autre E/S ne peut être démarrée sur le thread cible avant d’appeler la fonction CancelSynchronousIo ou CancelIoEx .

  • Nettoyez toutes les ressources créées dans la fonction de rappel avant de retourner à partir de la fonction . Il s’agit notamment du protocole TLS, des contextes de sécurité, de la priorité des threads et de l’inscription COM. Les fonctions de rappel doivent également restaurer l’état du thread avant de retourner.

  • Maintenez les handles d’attente et leurs objets associés actifs jusqu’à ce que le pool de threads ait signalé qu’il est terminé avec le handle.

  • Marquez tous les threads qui attendent des opérations longues (telles que les vidages d’E/S ou le nettoyage des ressources) afin que le pool de threads puisse allouer de nouveaux threads au lieu d’attendre celui-ci.

  • Avant de décharger une DLL qui utilise le pool de threads, annulez tous les éléments de travail, les E/S, les opérations d’attente et les minuteurs, puis attendez la fin de l’exécution des rappels.

  • Évitez les interblocages en éliminant les dépendances entre les éléments de travail et entre les rappels, en veillant à ce qu’un rappel n’attende pas qu’il se termine et en conservant la priorité du thread.

  • Ne placez pas trop d’éléments en file d’attente trop rapidement dans un processus avec d’autres composants à l’aide du pool de threads par défaut. Il existe un pool de threads par défaut par processus, y compris Svchost.exe. Par défaut, chaque pool de threads a un maximum de 500 threads de travail. Le pool de threads tente de créer davantage de threads de travail lorsque le nombre de threads de travail à l’état prêt/en cours d’exécution doit être inférieur au nombre de processeurs.

  • Évitez le modèle d’appartement monothread COM, car il est incompatible avec le pool de threads. STA crée l’état du thread qui peut affecter l’élément de travail suivant pour le thread. STA est généralement de longue durée et a une affinité de thread, qui est l’opposé du pool de threads.

  • Créez un pool de threads pour contrôler la priorité et l’isolation des threads, créer des caractéristiques personnalisées et éventuellement améliorer la réactivité. Toutefois, les pools de threads supplémentaires nécessitent davantage de ressources système (threads, mémoire du noyau). Un trop grand nombre de pools augmente le risque de contention du processeur.

  • Si possible, utilisez un objet d’attente au lieu d’un mécanisme basé sur APC pour signaler un thread de pool de threads. Les API ne fonctionnent pas aussi bien avec les threads de pool de threads que d’autres mécanismes de signalisation, car le système contrôle la durée de vie des threads du pool de threads. Il est donc possible qu’un thread soit arrêté avant la remise de la notification.

  • Utilisez l’extension de débogueur du pool de threads, !tp. Cette commande a l’utilisation suivante :

    • indicateurs d’adressede pool
    • Indicateursd’adresse obj
    • indicateurs d’adresse de file d’attente
    • adresse du serveur
    • adresse worker

    Pour le pool, le serveur et le worker, si l’adresse est égale à zéro, la commande vide tous les objets. Pour le serveur et le worker, l’omission de l’adresse vide le thread actuel. Les indicateurs suivants sont définis : 0x1 (sortie à ligne unique), 0x2 (membres de vidage) et 0x4 (file d’attente de travail du pool de vidages).

API de pool de threads

Utilisation des fonctions du pool de threads