Qu'est-ce qui constitue une bonne utilisation des threads dans la programmation?

13

Je suis fatigué d'entendre les gens vous recommander d'utiliser un seul thread par processeur, alors que de nombreux programmes en utilisent jusqu'à 100 par processus! prenez par exemple certains programmes communs

vb.net ide uses about 25 thread when not debugging
System uses about 100
chrome uses about 19
Avira uses more than about 50

Chaque fois que je poste une question liée à un thread, je me rappelle presque chaque fois que je ne devrais pas utiliser plus d'un thread par processeur, et tous les programmes que je mentionne ci-dessus ruinent mon système avec un seul processeur.

Forgeron
la source
7
Cette recommandation est trop large. La limite d'un thread par processeur n'est appropriée que pour les applications liées au calcul. La plupart des programmes sont liés aux E / S, que ce soit le trafic réseau, l'accès au disque ou même la RAM. C'est pourquoi les serveurs Web, les bases de données, etc. ont des pools de threads avec beaucoup plus de threads que de cœurs de processeur.
Kilian Foth
2
"Je me rappelle presque à chaque fois que je ne devrais pas utiliser plus d'un thread par processeur"? Pouvez-vous publier des liens ou des exemples? Presque à chaque fois?
S.Lott
2
"... les gens recommandent de n'utiliser qu'un seul thread par processus." Qui sont ces gens? La planification a considérablement progressé depuis l'âge des ténèbres.
Rein Henrichs
2
Vous ne devez pas avoir plus d'un thread d'interface utilisateur par processus.
SLaks
3
@Billy ONeal, votre modification a rendu la question vide de sens
SK-logic

Réponses:

22

vous ne devez utiliser qu'un seul thread par processeur,

Peut-être dans HPC où vous voulez une efficacité maximale - mais sinon la chose la plus stupide que j'ai entendue aujourd'hui!

Vous devez utiliser le nombre de threads qui sont appropriés pour la conception du programme et toujours donner des performances acceptables.

Pour un serveur Web, il peut être raisonnable de déclencher un thread pour chaque connexion entrante (bien qu'il existe de meilleures façons pour les serveurs très chargés).

Pour une idée, chaque outil exécuté dans son propre thread n'est pas déraisonnable. Je soupçonne que la plupart des threads signalés pour l'IDE .Net sont des choses comme la journalisation et les tâches d'E / S démarrées dans leurs propres threads afin qu'ils puissent continuer non bloqués.

Martin Beckett
la source
9
Vous me demandez maintenant quelle est la chose la plus stupide que vous ayez jamais entendue!
Michael K
3
@Michael - J'ai enseigné au premier cycle et travaillé sur des contrats de défense - vous ne croiriez pas les choses les plus stupides que j'ai entendues!
Martin Beckett
1
Les avons-nous vus sur TheDailyWTF.com?
FrustratedWithFormsDesigner
je ne peux pas vraiment les trouver maintenant, mais regardez ce lien social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/…
Smith
2
Avoir au plus un thread lié au processeur par processeur alloué à l'application. Les threads liés aux E / S ne sont pas un gros problème (autre que la mémoire qu'ils consomment) et il est important de se rappeler que les applications peuvent être limitées à n'utiliser qu'un sous-ensemble des processeurs du système; après tout, c'est (généralement) l'ordinateur de l'utilisateur / administrateur et non celui du programmeur.
Donal Fellows
2

L'avis d'un thread par cœur s'applique lorsque l'objectif est d'accélérer l'exécution parallèle.

Une raison complètement différente et tout aussi valable est la simplicité du code lorsqu'il doit répondre à des événements imprévisibles. Donc, si un programme doit écouter sur 100 sockets et semble accorder toute son attention à chacun, c'est une utilisation parfaite pour le filetage. Un autre exemple est une interface utilisateur, où un thread gère les événements d'interface utilisateur, tandis qu'un autre fait le traitement en arrière-plan.

Mike Dunlavey
la source
1
Le traitement lié aux E / S peut être effectué sous la forme d'un seul thread par source d'événements, ou plusieurs sources d'événements peuvent être multiplexées sur un seul thread. Le code multiplexé est généralement à la fois plus complexe et plus efficace.
Donal Fellows
2

Vous voulez un thread pour chaque calcul qui peut se dérouler à des rythmes différents des autres calculs.

Pour le calcul parallèle lié au CPU, qui vient en gros blocs de travail, vous voulez généralement un thread par CPU, car une fois qu'ils sont tous occupés, plus de threads n'aident pas et créent simplement une surcharge du planificateur. Si les blocs de travail ont des tailles irrégulières dans le temps ou sont générés dynamiquement au moment de l'exécution (cela se produit souvent lorsque vous avez de grandes structures de données complexes à traiter), vous souhaiterez peut-être attacher ces blocs à de nombreux threads, donc un planificateur a toujours un grand défini pour choisir à partir de la fin d'un certain bloc de travail, afin de garder tous les processeurs occupés.

Pour le calcul lié aux E / S, vous voulez généralement un thread pour chaque "canal" d'E / S indépendant car ils communiquent à des taux différents, et les threads bloqués sur le canal n'empêchent pas les autres threads de progresser.

Ira Baxter
la source
Sachez simplement que ce style de threading peut conduire à des programmes étrangement architecturés. J'ai vu un programme à 4 threads qui avait un thread pour lire les enregistrements d'une table de base de données, un thread pour écrire les enregistrements transformés dans un socket, un thread pour lire les réponses à ces écritures de socket (qui sont revenues dans le désordre) et de manière asynchrone), et un thread pour modifier l'enregistrement DB d'origine avec la réponse. Des conditions d'erreur peu intuitives s'ensuivirent.
Bruce Ediger
Une vue est que ce style produit des programmes impairs. Un autre point de vue est le style naturel que les programmes auraient dû avoir. Ne sais pas sur les conditions d'erreur "non intuitives"; si beaucoup de choses se produisent, et que l'une d'elles reçoit une erreur, s'assurer qu'elle se propage correctement à travers les calculs asynchrones est un problème pour de nombreux langages [stupidement, les exceptions Java ne sont pas définies aux limites des threads], mais ne le sont pas un problème avec le style de programme. (Notre langage de programmation PARLANSE [voir ma biographie] gère proprement les exceptions au-delà des limites des threads, il est donc possible de le faire correctement.).
Ira Baxter
1

La règle générale pour les threads est que vous voulez au moins un thread de travail "actif" (capable d'exécuter ses commandes immédiatement en fonction du temps CPU) pour chaque "unité d'exécution" disponible sur l'ordinateur. Une «unité d'exécution» est un processeur d'instructions logiques, donc un serveur hyperthreadé Xeon quadruple puce quadruple cœur aurait 32 EU (4 puces, 4 cœurs par puce, chaque hyperthreadé). Votre Core i7 moyen en aurait 8.

Un thread par UE est l'utilisation la plus complète de la puissance du processeur, à condition que les threads soient toujours en état de fonctionnement; ce n'est presque jamais le cas, car les threads doivent avoir accès à la mémoire non mise en cache, au disque dur, aux ports réseau, etc. qu'ils doivent attendre et qui ne nécessitent pas d'attention active du processeur pour fonctionner. Vous pouvez ainsi augmenter encore l'efficacité globale avec plus de threads en file d'attente et prêts à partir. Cela a un coût; lorsqu'un CPU commute un thread, il doit mettre en cache les registres du thread, le pointeur d'exécution et d'autres informations d'état normalement conservés dans le fonctionnement le plus interne d'une UE et accessibles très rapidement, permettant aux autres EU de cette puce de CPU de les récupérer. Il nécessite également des threads dans le système d'exploitation pour décider quel thread doit être basculé. Enfin, lorsqu'une UE change de fil, il perd les gains de performances du pipeline que la plupart des architectures de processeurs utilisent; il doit vider le pipeline avant de changer de threads. Mais, comme tout cela prend encore beaucoup moins de temps en moyenne que d'attendre simplement que le disque dur ou même la RAM revienne avec des informations, cela en vaut le coût.

Cependant, en général, une fois que vous avez dépassé le double du nombre de threads "actifs" en tant qu'UE, le système d'exploitation commence à consacrer plus de temps aux threads de planification de l'UE et les UE passent plus de temps à basculer entre eux que ce qui est réellement passé à exécuter des threads actifs. des programmes. C'est le point des déséconomies d'échelle; il faudra en fait plus de temps pour qu'un algorithme multithread s'exécute si vous deviez ajouter un thread supplémentaire à ce stade.

Donc, dans l'ensemble, vous voulez conserver au moins autant de threads dans votre programme que vous avez d'UE sur l'ordinateur, mais vous voulez éviter d'avoir plus du double de ce nombre qui n'attendent pas ou ne dorment pas.

KeithS
la source
Si N est le nombre de threads et U le nombre d'unités, l'OP a remis en question la règle "N = U". Vous l'assouplissez à une règle "U <= N <= 2 U". J'irais un peu plus loin et dirais que "N <= c U" pour une constante "raisonnablement petite" (connue du programmeur) c est acceptable (si les repères montrent des performances raisonnables). Je serais très inquiet si le nombre de threads pouvait atteindre un nombre potentiellement illimité.
5gon12eder
1

Vous devez utiliser un fil pour:

Chaque processeur dont vous avez besoin pour rester occupé.

Chaque E / S que vous pouvez utilement suspendre simultanément que vous ne pouvez pas effectuer de manière non bloquante. (Par exemple, lit à partir d'un disque local.)

Chaque tâche qui nécessite un thread dédié, par exemple l'appel à une bibliothèque qui n'a pas d'interface non bloquante ou où les interfaces non bloquantes ne sont pas appropriées. Cela inclut des tâches telles que la surveillance de l'horloge système, le déclenchement des minuteries, etc.

Quelques extra pour se protéger contre les blocages inattendus tels que les défauts de page.

Quelques éléments supplémentaires pour se protéger contre le blocage attendu qui ne vaut pas la peine d'être optimisé, par exemple dans le code non critique. (Par exemple, si vous avez très rarement besoin de faire une requête DNS, cela ne vaut probablement pas la peine de faire des requêtes DNS de manière asynchrone. Créez simplement quelques threads supplémentaires et simplifiez-vous la vie.)

Si vous suivez la règle "un thread par processeur", alors tout votre code est critique pour les performances. Tout code qui se bloque pour une raison quelconque signifie que votre processus ne peut pas utiliser ce processeur. Cela rend la programmation beaucoup plus difficile sans raison valable.

David Schwartz
la source
0

Vous pouvez générer des processus et des threads pour permettre l'utilisation d'un système multicœur \ multiprocesseur pour un seul programme, auquel cas vous ne gagnez aucun avantage (pour le programme unique au moins) à avoir plus de threads \ processus que de cœurs.

Ou vous pouvez avoir des routines qui interrogent un événement qui bloquent généralement la poursuite de l'exécution. Plutôt que de lier le CPU à l'interrogation, vous pouvez créer un thread qui restera inactif jusqu'à ce que l'événement approprié le réveille. Cette méthode est très couramment utilisée dans les serveurs Web et les files d'attente d'événements GUI. La plupart des programmes veulent avoir une sorte de magasin de données central (même si son code d'exécution de programme) auquel tous les threads peuvent accéder, donc je suppose que c'est pourquoi ils utilisent le threading sur les processus.

Peter Smith
la source
0

Les applications que vous mentionnez exécutent rarement toutes ces dizaines de threads simultanément. La plupart d'entre eux restent assis parce qu'ils sont dans un pool de threads . L'application envoie diverses tâches à une file d'attente, qui est purgée par les threads du pool de threads.

Pourquoi la piscine est-elle si grande alors? Parce que, souvent, les threads doivent attendre d'autres ressources telles que le disque, le réseau, l'utilisateur, un autre thread, etc. Pendant qu'un thread attend, il est approprié d'exécuter d'autres threads pour utiliser pleinement le processeur. Le dimensionnement approprié de la piscine est cependant délicat. Trop peu de threads et vous perdrez les performances car le processeur n'est pas pleinement utilisé en attendant quelque chose. Trop de threads et vous perdrez les performances en changeant entre eux.

Joonas Pulakka
la source