Combien de fils, c'est trop?

312

J'écris un serveur et j'envoie chaque action de dans un thread séparé lorsque la demande est reçue. Je le fais parce que presque chaque demande fait une requête de base de données. J'utilise une bibliothèque de threadpool pour réduire la construction / destruction des threads.

Ma question est: quel est un bon point de coupure pour les threads d'E / S comme ceux-ci? Je sais que ce ne serait qu'une estimation approximative, mais parlons-nous de centaines? Milliers?

Comment pourrais-je déterminer ce que serait cette coupure?


ÉDITER:

Merci à tous pour vos réponses, il semble que je vais juste devoir le tester pour connaître mon plafond de nombre de threads. La question est cependant: comment savoir si j'ai atteint ce plafond? Que dois-je mesurer exactement?

ryeguy
la source
1
@ryeguy: Le point ici est que vous ne devez pas définir de maximum dans le pool de threads s'il n'y a pas de problèmes de performances pour commencer. La plupart des conseils pour limiter un pool de threads à ~ 100 threads sont ridicules, la plupart des pools de threads ont / way / more threads que cela et n'ont jamais de problème.
GEOCHET
ryeguy, voir l'addition à ma réponse ci-dessous re quoi mesurer.
paxdiablo
N'oubliez pas que Python est par nature, pas vraiment convivial multi-thread. À tout moment, un seul opcode bytecode est en cours d'exécution. En effet, Python utilise Global Interpreter Lock.
Demander
1
@Jay D: Je dirais que le moment où vous avez atteint le plafond, c'est quand votre performance commence à baisser.
ninjalj
6
@GEOCHET "Le point ici est que vous ne devez pas définir de maximum dans le pool de threads" Ummm ... dites quoi? Les pools de threads de taille fixe ont les avantages d'une dégradation et d'une évolutivité gracieuses. Par exemple, dans un environnement réseau, si vous générez de nouveaux threads basés sur des connexions client, sans une taille de pool fixe, vous courez le danger très réel d'apprendre (à la dure ) le nombre de threads que votre serveur peut gérer et chaque client connecté unique. va souffrir. Un pool de taille fixe agit comme une vanne de tuyau en empêchant votre serveur d'essayer de mordre plus qu'il ne peut mâcher.
b1nary.atr0phy

Réponses:

206

Certaines personnes diraient que deux fils, c'est trop - je ne suis pas tout à fait dans ce camp :-)

Voici mon conseil: mesurez, ne devinez pas. Une suggestion est de le rendre configurable et de le régler initialement sur 100, puis de libérer votre logiciel à l'état sauvage et de surveiller ce qui se passe.

Si votre utilisation de thread atteint un pic à 3, alors 100 est trop. S'il reste à 100 pendant la majeure partie de la journée, augmentez-le jusqu'à 200 et voyez ce qui se passe.

Vous pourriez en fait avoir votre code lui-même surveiller l'utilisation et ajuster la configuration pour le prochain démarrage, mais c'est probablement exagéré.


Pour clarification et élaboration:

Je ne préconise pas de rouler votre propre sous-système de pool de threads, utilisez certainement celui que vous avez. Mais, puisque vous posiez des questions sur un bon point de coupure pour les threads, je suppose que votre implémentation de pool de threads a la capacité de limiter le nombre maximum de threads créés (ce qui est une bonne chose).

J'ai écrit du code de regroupement de connexions de threads et de bases de données et ils ont les fonctionnalités suivantes (qui, selon moi, sont essentielles pour les performances):

  • un nombre minimum de threads actifs.
  • un nombre maximum de threads.
  • fermer les threads qui n'ont pas été utilisés depuis un certain temps.

Le premier définit une ligne de base pour des performances minimales en termes de client de pool de threads (ce nombre de threads est toujours disponible pour utilisation). Le second définit une restriction sur l'utilisation des ressources par les threads actifs. Le troisième vous ramène à la ligne de base en temps calme afin de minimiser l'utilisation des ressources.

Vous devez équilibrer l'utilisation des ressources d'avoir des threads inutilisés (A) contre l'utilisation des ressources de ne pas avoir suffisamment de threads pour effectuer le travail (B).

(A) correspond généralement à l'utilisation de la mémoire (piles, etc.), car un thread qui ne fonctionne pas n'utilisera pas une grande partie du processeur. (B) sera généralement un retard dans le traitement des demandes à mesure qu'elles arrivent, car vous devez attendre qu'un thread soit disponible.

Voilà pourquoi vous mesurez. Comme vous le dites, la grande majorité de vos threads attendront une réponse de la base de données pour ne pas s'exécuter. Il y a deux facteurs qui affectent le nombre de threads que vous devez autoriser.

Le premier est le nombre de connexions DB disponibles. Cela peut être une limite stricte sauf si vous pouvez l'augmenter au niveau du SGBD - je vais supposer que votre SGBD peut prendre un nombre illimité de connexions dans ce cas (bien que vous devriez idéalement mesurer cela également).

Ensuite, le nombre de threads que vous devriez avoir dépend de votre utilisation historique. Le minimum que vous devriez avoir en cours d'exécution est le nombre minimum que vous avez déjà eu en cours d'exécution + A%, avec un minimum absolu de (par exemple, et le rendre configurable comme A) 5.

Le nombre maximal de threads doit être votre maximum historique + B%.

Vous devez également surveiller les changements de comportement. Si, pour une raison quelconque, votre utilisation atteint 100% de la disponibilité pendant un temps significatif (de sorte que cela affecterait les performances des clients), vous devriez augmenter le maximum autorisé jusqu'à ce qu'il soit à nouveau B% plus élevé.


En réponse au "que dois-je mesurer exactement?" question:

Ce que vous devez mesurer spécifiquement, c'est la quantité maximale de threads en utilisation simultanée (par exemple, en attente d'un retour de l'appel DB) sous charge. Ajoutez ensuite un facteur de sécurité de 10% par exemple (souligné, car d'autres affiches semblent prendre mes exemples comme des recommandations fixes).

De plus, cela doit être fait dans l'environnement de production pour le réglage. Il est normal d'obtenir une estimation au préalable, mais vous ne savez jamais quelle production va vous lancer (c'est pourquoi toutes ces choses devraient être configurables au moment de l'exécution). Il s'agit d'attraper une situation telle qu'un doublement inattendu des appels clients entrant.

paxdiablo
la source
Si les threads sont générés sur les requêtes entrantes, l'utilisation des threads reflétera le nombre de requêtes non traitées. Il n'y a aucun moyen de déterminer le nombre "optimal" à partir de cela. En effet, vous trouverez plus de threads provoquant plus de conflits de ressources et donc le nombre de threads actifs augmentera.
Andrew Grant
@Andrew, la création de threads prend du temps et vous pouvez déterminer le nombre optimal en fonction des données historiques [+ N%] (mesure donc, ne devinez pas). En outre, plusieurs threads ne provoquent des conflits de ressources que lorsqu'ils effectuent un travail, sans attendre un signal / sémaphore.
paxdiablo
Où ces données sur la «création de threads» provoquent-elles un problème de performances lors de l'utilisation d'un pool de threads? Un bon pool de threads ne créerait pas et ne détruirait pas les threads entre les tâches.
GEOCHET
@Pax Si tous vos threads attendent sur les mêmes sémaphores pour exécuter des requêtes DB, c'est la définition même de la contention. Il est également faux de dire que les threads ne coûtent rien s'ils attendent sur un sémaphore.
Andrew Grant
1
@Andrew, je ne vois pas pourquoi vous avez bloqué les requêtes de base de données par sémaphore, toute base de données décente permettra un accès simultané, avec de nombreux threads en attente des réponses. Et les threads ne devraient pas coûter de temps d'exécution lorsqu'ils sont bloqués par le sémaphore, ils doivent rester dans la file d'attente bloquée jusqu'à ce que le sémaphore soit libéré.
paxdiablo
36

Cette question a été discutée de manière assez approfondie et je n'ai pas eu la chance de lire toutes les réponses. Mais voici quelques éléments à prendre en considération lors de l'examen de la limite supérieure du nombre de threads simultanés qui peuvent coexister pacifiquement dans un système donné.

  1. Taille de la pile de threads: Sous Linux, la taille de la pile de threads par défaut est de 8 Mo (vous pouvez utiliser ulimit -a pour le découvrir).
  2. Mémoire virtuelle maximale prise en charge par une variante de système d'exploitation donnée. Linux Kernel 2.4 prend en charge un espace d'adressage mémoire de 2 Go. avec Kernel 2.6, je suis un peu plus gros (3 Go)
  3. [1] montre les calculs du nombre maximal de threads par machine virtuelle maximale prise en charge donnée. Pour 2.4, il s'avère être d'environ 255 threads. pour 2.6, le nombre est un peu plus grand.
  4. Quel planificateur de noyau kindda vous avez. En comparant le planificateur de noyau Linux 2.4 avec 2.6, le dernier vous donne une planification O (1) sans dépendance sur le nombre de tâches existant dans un système tandis que la première est plus un O (n). Ainsi, les capacités SMP de la planification du noyau jouent également un bon rôle dans le nombre maximal de threads durables dans un système.

Vous pouvez maintenant ajuster la taille de votre pile pour incorporer plus de threads, mais vous devez ensuite prendre en compte les frais généraux de gestion des threads (création / destruction et planification). Vous pouvez appliquer l'affinité CPU à un processus donné ainsi qu'à un thread donné pour les lier à des CPU spécifiques afin d'éviter les frais généraux de migration de threads entre les CPU et d'éviter les problèmes de trésorerie.

Notez que l'on peut créer des milliers de threads à sa guise, mais lorsque Linux manque de VM, il commence juste à tuer des processus au hasard (donc des threads). C'est pour éviter que le profil de l'utilitaire ne soit optimisé. (La fonction utilitaire indique l'utilité à l'échelle du système pour une quantité donnée de ressources. Avec des ressources constantes dans ce cas, Cycles CPU et mémoire, la courbe d'utilité s'aplatit avec de plus en plus de tâches).

Je suis sûr que le planificateur du noyau Windows fait quelque chose de ce genre pour gérer la surutilisation des ressources

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/

Jay D
la source
17

Si vos threads effectuent tout type de travail gourmand en ressources (CPU / disque), vous verrez rarement des avantages au-delà d'un ou deux, et un trop grand nombre réduira les performances très rapidement.

Le `` meilleur des cas '' est que vos derniers threads se bloqueront pendant que les premiers se termineront, ou certains auront des blocs à faible surcharge sur les ressources à faible conflit. Le pire des cas est que vous commencez à écraser le cache / disque / réseau et votre débit global chute à travers le sol.

Une bonne solution consiste à placer des demandes dans un pool qui sont ensuite envoyées aux threads de travail à partir d'un pool de threads (et oui, éviter la création / destruction continue de threads est une excellente première étape).

Le nombre de threads actifs dans ce pool peut ensuite être modifié et mis à l'échelle en fonction des résultats de votre profilage, du matériel sur lequel vous exécutez et d'autres choses qui peuvent se produire sur la machine.

Andrew Grant
la source
Oui, et il doit être utilisé conjointement avec une file d'attente ou un pool de demandes.
Andrew Grant
2
@Andrew: Pourquoi? Il doit ajouter une tâche au pool de threads chaque fois qu'il reçoit une demande. Il appartient au pool de threads d'allouer un thread à la tâche lorsqu'il en existe un.
GEOCHET
Alors, que faites-vous lorsque vous avez des centaines de demandes qui arrivent et sont hors de threads? Créez plus? Bloquer? Renvoyer une erreur? Placez vos demandes dans un pool qui peut être aussi grand que nécessaire, puis alimentez ces requêtes en file d'attente dans votre pool de threads à mesure que les threads deviennent libres.
Andrew Grant
"un certain nombre de threads sont créés pour effectuer un certain nombre de tâches, qui sont généralement organisées dans une file d'attente. En général, il y a beaucoup plus de tâches que de threads. Dès qu'un thread termine sa tâche, il demande la tâche suivante à la file d'attente jusqu'à ce que toutes les tâches soient terminées. "
GEOCHET
@Andrew: Je ne sais pas quel pool de threads python l'OP utilise, mais si vous voulez un exemple réel de cette fonctionnalité, je décris: msdn.microsoft.com/en-us/library/…
GEOCHET
10

Une chose que vous devez garder à l'esprit est que python (au moins la version basée sur C) utilise ce qu'on appelle un verrou d'interpréteur global qui peut avoir un impact énorme sur les performances sur les machines multicœurs.

Si vous avez vraiment besoin de tirer le meilleur parti du python multithread, vous voudrez peut-être envisager d'utiliser Jython ou quelque chose.

Chad Okere
la source
4
Après avoir lu ceci, j'ai essayé d'exécuter le tamisage des tâches d'Eratosthène sur trois threads. Effectivement, il était en fait 50% plus lent que l'exécution des mêmes tâches dans un seul thread. Merci pour l'information. J'exécutais Eclipse Pydev sur une machine virtuelle à laquelle étaient alloués deux CPU. Ensuite, je vais essayer un scénario qui implique certains appels de base de données.
Don Kirkby
3
Il existe deux (au moins) types de tâches: liées au processeur (par exemple, traitement d'image) et liées aux E / S (par exemple, téléchargement à partir du réseau). De toute évidence, le "problème" GIL n'affectera pas trop les tâches liées aux E / S. Si vos tâches sont liées au processeur, vous devriez envisager le multitraitement au lieu du multithreading.
iutinvg
1
oui, le thread python s'est amélioré si vous avez beaucoup de réseau io.Je le change en thread et j'ai obtenu 10 * plus rapidement que le code ordinaire ...
tyan
8

Comme l'a dit à juste titre Pax, mesurez, ne devinez pas . C'est ce que j'ai fait pour DNSwitness et les résultats ont été surprenants: le nombre idéal de threads était beaucoup plus élevé que je ne le pensais, quelque chose comme 15 000 threads pour obtenir les résultats les plus rapides.

Bien sûr, cela dépend de beaucoup de choses, c'est pourquoi vous devez vous mesurer.

Mesures complètes (en français seulement) dans Combien de fils d'exécution? .

bortzmeyer
la source
1
15 000? C'est un peu plus que ce à quoi je m'attendais également. Pourtant, si c'est ce que vous avez, alors c'est ce que vous avez, je ne peux pas contester cela.
paxdiablo
2
Pour cette application spécifique, la plupart des threads attendent simplement une réponse du serveur DNS. Donc, plus il y a de parallélisme, mieux c'est en temps d'horloge murale.
bortzmeyer le
18
Je pense que si vous avez ces 15000 threads qui bloquent certaines E / S externes, alors une meilleure solution serait massivement moins de threads mais avec un modèle asynchrone. Je parle d'expérience ici.
Steve
5

J'ai écrit un certain nombre d'applications fortement multi-thread. J'autorise généralement le nombre de threads potentiels à être spécifié par un fichier de configuration. Lorsque j'ai réglé pour des clients spécifiques, j'ai défini un nombre suffisamment élevé pour que mon utilisation de tous les cœurs de processeur soit assez élevée, mais pas si élevée que j'ai rencontré des problèmes de mémoire (il s'agissait de systèmes d'exploitation 32 bits au temps).

Autrement dit, une fois que vous avez atteint un goulot d'étranglement, que ce soit le processeur, le débit de la base de données, le débit du disque, etc., l'ajout de threads n'augmentera pas les performances globales. Mais jusqu'à ce que vous atteigniez ce point, ajoutez plus de discussions!

Notez que cela suppose que le ou les systèmes en question sont dédiés à votre application et que vous n'avez pas besoin de bien jouer (évitez de mourir de faim) d'autres applications.

Matthew Lund
la source
1
Pouvez-vous mentionner certains des chiffres que vous avez vus pour le nombre de threads? Il serait utile de simplement en avoir une idée. Merci.
kovac
3

La réponse "Big Iron" est généralement un thread par ressource limitée - processeur (lié au CPU), armé (lié aux E / S), etc. - mais cela ne fonctionne que si vous pouvez acheminer le travail vers le thread approprié pour la ressource. être accessible.

Lorsque cela n'est pas possible, considérez que vous disposez de ressources fongibles (CPU) et de ressources non fongibles (bras). Pour les processeurs, il n'est pas essentiel d'attribuer chaque thread à un processeur spécifique (bien que cela aide à la gestion du cache), mais pour les bras, si vous ne pouvez pas affecter un thread au bras, vous entrez dans la théorie de la file d'attente et quel est le nombre optimal pour garder les bras occupé. En général, je pense que si vous ne pouvez pas acheminer les demandes en fonction du bras utilisé, alors avoir 2-3 threads par bras sera à peu près correct.

Une complication survient lorsque l'unité de travail transmise au thread n'exécute pas une unité de travail raisonnablement atomique. Par exemple, vous pouvez avoir le thread à un moment donné accéder au disque, à un autre moment attendre sur un réseau. Cela augmente le nombre de «fissures» où des threads supplémentaires peuvent entrer et faire un travail utile, mais cela augmente également la possibilité pour les threads supplémentaires de polluer les caches les uns des autres, etc., et de bloquer le système.

Bien sûr, vous devez peser tout cela contre le «poids» d'un fil. Malheureusement, la plupart des systèmes ont des threads très lourds (et ce qu'ils appellent souvent des «threads légers» ne sont pas du tout des threads), il est donc préférable de se tromper sur le côté bas.

Ce que j'ai vu dans la pratique, c'est que des différences très subtiles peuvent faire une énorme différence dans le nombre de threads qui sont optimaux. En particulier, les problèmes de cache et les conflits de verrouillage peuvent limiter considérablement la quantité de simultanéité pratique.

Hot Licks
la source
2

Une chose à considérer est le nombre de cœurs qui existent sur la machine qui exécutera le code. Cela représente une limite stricte sur le nombre de threads pouvant se poursuivre à un moment donné. Cependant, si, comme dans votre cas, les threads attendent fréquemment qu'une base de données exécute une requête, vous souhaiterez probablement ajuster vos threads en fonction du nombre de requêtes simultanées que la base de données peut traiter.

newdayrising
la source
2
um non. L'intérêt des threads (avant que le multicœur et les processeurs multiples ne deviennent courants) est de pouvoir imiter plusieurs processeurs sur une machine qui n'en a qu'un. C'est ainsi que vous obtenez des interfaces utilisateur réactives - un thread principal et des threads auxiliaires.
MMR
1
@mmr: Um non. L'idée des threads est de permettre le blocage des E / S et d'autres tâches.
GEOCHET
4
La déclaration que j'ai faite était que le nombre de cœurs sur une machine représente une limite stricte sur le nombre de threads qui peuvent travailler à un moment donné, ce qui est un fait. Bien sûr, d'autres threads peuvent attendre la fin des opérations d'E / S, et cette question est une considération importante.
newdayrising
1
Quoi qu'il en soit - vous avez GIL en Python, ce qui rend les threads uniquement théoriquement parallèles. Pas plus d'un thread ne peut s'exécuter simultanément, c'est donc uniquement la réactivité et les opérations de blocage qui comptent.
Abgan
2
+1 Pour réellement comprendre le fonctionnement des ordinateurs. @mmr: Vous devez comprendre la différence entre semble avoir plusieurs processeurs et a plusieurs processeurs. @ Rich B: Un pool de threads n'est qu'une des nombreuses façons de gérer une collection de threads. Elle est bonne, mais certainement pas la seule.
pleurer le
2

Je pense que c'est un peu une esquive à votre question, mais pourquoi ne pas les fourrer dans les processus? Ma compréhension du réseautage (depuis les jours brumeux d'autrefois, je ne code pas vraiment les réseaux du tout) était que chaque connexion entrante peut être traitée comme un processus distinct, car si quelqu'un fait quelque chose de méchant dans votre processus, il ne le fait pas nuke l'ensemble du programme.

mmr
la source
1
Pour Python, cela est particulièrement vrai, car plusieurs processus peuvent s'exécuter en parallèle, contrairement à plusieurs threads. Le coût est cependant assez élevé. Vous devez démarrer un nouvel interpréteur Python à chaque fois et vous connecter à la base de données avec chaque processus (ou utiliser la redirection de certains tuyaux, mais cela a également un prix).
Abgan
La commutation entre les processus est - la plupart du temps - plus coûteuse que la commutation entre les threads (changement de contexte entier au lieu de certains registres). À la fin, cela dépend fortement de votre threading-lib. Comme les questions tournaient autour du filetage, je suppose que les processus sont déjà hors de question.
Leonidas
C'est suffisant. Je ne sais pas pourquoi c'est pourquoi j'obtiens un score de -2 au score, à moins que les gens ne veuillent vraiment voir des réponses en mode thread uniquement, plutôt que d'inclure d'autres réponses qui fonctionnent.
mmr
@mmr: Considérant que la question portait sur / thread / pools, oui, je pense que les gens devraient s'attendre à une réponse sur les threads.
GEOCHET
La création de processus peut être effectuée une fois au démarrage (c'est-à-dire un pool de processus au lieu d'un pool de threads). Amorti sur la durée d'application, il peut être faible. Ils ne peuvent pas partager facilement les informations, mais cela leur donne la possibilité de fonctionner sur plusieurs processeurs, donc cette réponse est utile. +1.
paxdiablo
1

ryeguy, je développe actuellement une application similaire et mon nombre de threads est réglé sur 15. Malheureusement si je l'augmente à 20, ça plante. Donc, oui, je pense que la meilleure façon de gérer cela est de mesurer si votre configuration actuelle autorise plus ou moins un certain nombre de threads.

hyperboreean
la source
5
L'ajout à votre nombre de threads ne doit pas provoquer de plantage aléatoire de votre application. Il y a une raison. Vous feriez bien de comprendre la cause car cela peut vous affecter même avec moins de threads dans certaines circonstances, qui sait.
Matthew Lund
-6

Dans la plupart des cas, vous devez autoriser le pool de threads à gérer cela. Si vous publiez du code ou donnez plus de détails, il pourrait être plus facile de voir s'il y a une raison pour laquelle le comportement par défaut du pool de threads ne serait pas le meilleur.

Vous pouvez trouver plus d'informations sur la façon dont cela devrait fonctionner ici: http://en.wikipedia.org/wiki/Thread_pool_pattern

GEOCHET
la source
1
@Pax: Ce ne serait pas la première fois que la majorité des gens ne veulent pas répondre à la question posée (ou la comprendre). Je ne suis pas inquiet.
GEOCHET
-10

Autant de threads que de cœurs CPU est ce que j'ai entendu très souvent.

masfenix
la source
5
@Rich, expliquez au moins pourquoi :-). Cette règle empirique s'applique uniquement lorsque tous les threads sont liés au processeur; ils obtiennent chacun un «CPU». Lorsque de nombreux threads sont liés aux E / S, il est généralement préférable d'avoir beaucoup plus de threads que de CPU (CPU est cité car il s'applique aux threads physiques d'exécution, par exemple les cœurs).
paxdiablo
1
@Abgan, je n'en étais pas sûr, pensant peut-être que Python créerait de "vrais" threads d'OS (exécutés sur plusieurs CPU). Si ce que vous dites est vrai (je n'ai aucune raison de douter), alors la quantité de CPU n'a pas d'incidence - le threading n'est utile que lorsque la plupart des threads attendent quelque chose (par exemple, des E / S DB).
paxdiablo
1
@Rich: lorsque le thread (réel), le nombre de processeurs a une incidence, car vous pouvez exécuter plusieurs threads sans attente de manière vraiment simultanée. Avec un seul processeur, un seul s'exécute et l'avantage est d'avoir de nombreux autres threads en attente d'une ressource non CPU.
paxdiablo
1
@Pax: Vous ne comprenez pas le concept de pools de threads, alors je suppose.
GEOCHET
1
@ Rich, je comprends très bien les pools de threads; il semble que moi (et d'autres ici) comprenne mieux le matériel que vous. Avec un processeur, un seul thread d'exécution peut s'exécuter, même s'il y en a d'autres en attente d'un processeur. Deux processeurs, deux peuvent fonctionner. Si tous les threads attendent un CPU, le nombre de threads idéal est égal à ...
paxdiablo