Je pensais que l'intérêt d'un ordinateur multicœur était qu'il pouvait exécuter plusieurs threads simultanément. Dans ce cas, si vous avez une machine quad-core, quel est l'intérêt d'avoir plus de 4 threads en cours d'exécution à la fois? Ne seraient-ils pas simplement en train de voler du temps (ressources CPU) les uns aux autres?
multithreading
hardware
cpu-cores
Nick Heiner
la source
la source
Réponses:
La réponse tourne autour de l'objectif des threads, qui est le parallélisme: exécuter plusieurs lignes d'exécution distinctes à la fois. Dans un système «idéal», vous auriez un thread s'exécutant par cœur: pas d'interruption. En réalité, ce n'est pas le cas. Même si vous avez quatre cœurs et quatre threads de travail, votre processus et ses threads seront constamment remplacés par d'autres processus et threads. Si vous exécutez un système d'exploitation moderne, chaque processus a au moins un thread, et beaucoup en ont plus. Tous ces processus s'exécutent en même temps. Vous avez probablement plusieurs centaines de threads en cours d'exécution sur votre machine en ce moment. Vous n'obtiendrez jamais une situation où un thread s'exécute sans que le temps ne lui soit `` volé ''. (Eh bien, vous pourriez si cela fonctionne en temps réel, si vous utilisez un système d'exploitation en temps réel ou, même sous Windows, utilisez une priorité de thread en temps réel. Mais c'est rare.)
Avec cela en arrière-plan, la réponse: Oui, plus de quatre threads sur une véritable machine à quatre cœurs peuvent vous donner une situation où ils `` volent du temps l'un à l'autre '', mais seulement si chaque thread individuel a besoin de 100% de CPU . Si un thread ne fonctionne pas à 100% (comme un thread d'interface utilisateur peut ne pas l'être, ou un thread effectuant une petite quantité de travail ou attendant quelque chose d'autre), un autre thread en cours de planification est en fait une bonne situation.
C'est en fait plus compliqué que ça:
Et si vous avez cinq morceaux de travail qui doivent tous être effectués en même temps? Il est plus logique de les exécuter tous en même temps que d'en exécuter quatre, puis d'exécuter le cinquième plus tard.
Il est rare qu'un thread ait réellement besoin de 100% de CPU. Au moment où il utilise des E / S disque ou réseau, par exemple, il peut passer du temps à attendre à ne rien faire d'utile. C'est une situation très courante.
Si vous avez du travail à exécuter, un mécanisme courant consiste à utiliser un pool de threads. Il peut sembler judicieux d'avoir le même nombre de threads que les cœurs, mais le pool de threads .Net a jusqu'à 250 threads disponibles par processeur . Je ne sais pas pourquoi ils font cela, mais je suppose que c'est à voir avec la taille des tâches qui sont données pour s'exécuter sur les threads.
Donc: voler du temps n'est pas une mauvaise chose (et ce n'est pas vraiment un vol non plus: c'est ainsi que le système est censé fonctionner.) Écrivez vos programmes multithread en fonction du type de travail que les threads feront, qui peut ne pas être du processeur -lié. Déterminez le nombre de threads dont vous avez besoin en fonction du profilage et de la mesure. Vous trouverez peut-être plus utile de penser en termes de tâches ou de travaux, plutôt que de threads: écrivez des objets de travail et donnez-les à un pool à exécuter. Enfin, à moins que votre programme ne soit vraiment critique pour les performances, ne vous inquiétez pas trop :)
la source
Le fait qu'un thread existe ne signifie pas toujours qu'il est en cours d'exécution. De nombreuses applications de threads impliquent que certains threads se mettent en veille jusqu'à ce qu'il soit temps pour eux de faire quelque chose - par exemple, l'entrée utilisateur déclenche les threads pour se réveiller, effectuer un traitement et se rendormir.
Essentiellement, les threads sont des tâches individuelles qui peuvent fonctionner indépendamment les unes des autres, sans avoir besoin d'être conscient de la progression d'une autre tâche. Il est tout à fait possible d'en avoir plus que vous ne pouvez en exécuter simultanément; ils sont toujours utiles pour plus de commodité, même s'ils doivent parfois faire la queue les uns derrière les autres.
la source
Le fait est que, même si vous n'obtenez aucune accélération réelle lorsque le nombre de threads dépasse le nombre de cœurs, vous pouvez utiliser des threads pour démêler des éléments de logique qui ne devraient pas avoir à être interdépendants.
Dans une application même modérément complexe, utiliser un seul thread, essayer de tout faire rapidement, fait hacher le «flux» de votre code. Le thread unique passe la plupart de son temps à interroger cela, à vérifier cela, à appeler des routines de manière conditionnelle si nécessaire, et il devient difficile de voir autre chose qu'un bourbier de minuties.
Comparez cela avec le cas où vous pouvez dédier des threads à des tâches afin que, en regardant n'importe quel thread individuel, vous puissiez voir ce que fait ce thread. Par exemple, un thread peut bloquer l'attente sur l'entrée d'une socket, analyser le flux en messages, filtrer les messages et, lorsqu'un message valide arrive, le transmettre à un autre thread de travail. Le thread de travail peut travailler sur des entrées provenant d'un certain nombre d'autres sources. Le code de chacun d'entre eux affichera un flux propre et ciblé, sans avoir à faire de vérifications explicites qu'il n'y a rien d'autre à faire.
Partitionner le travail de cette manière permet à votre application de s'appuyer sur le système d'exploitation pour planifier ce qu'il faut faire ensuite avec le processeur, vous n'avez donc pas à effectuer des vérifications conditionnelles explicites partout dans votre application sur ce qui pourrait bloquer et ce qui est prêt à traiter.
la source
Si un thread attend une ressource (comme charger une valeur de la RAM dans un registre, des E / S disque, un accès réseau, lancer un nouveau processus, interroger une base de données ou attendre l'entrée de l'utilisateur), le processeur peut travailler sur un thread différent et revenez au premier thread une fois que la ressource est disponible. Cela réduit le temps que le processeur passe inactif, car le processeur peut effectuer des millions d'opérations au lieu de rester inactif.
Considérez un thread qui a besoin de lire des données sur un disque dur. En 2014, un cœur de processeur typique fonctionne à 2,5 GHz et peut être capable d'exécuter 4 instructions par cycle. Avec un temps de cycle de 0,4 ns, le processeur peut exécuter 10 instructions par nanoseconde. Avec des temps de recherche mécaniques typiques du disque dur d'environ 10 millisecondes, le processeur est capable d'exécuter 100 millions d'instructions dans le temps nécessaire pour lire une valeur sur le disque dur. Il peut y avoir des améliorations significatives des performances avec les disques durs avec un petit cache (4 Mo de mémoire tampon) et les disques hybrides avec quelques Go de stockage, car la latence des données pour les lectures séquentielles ou les lectures à partir de la section hybride peut être plusieurs ordres de grandeur plus rapide.
Un cœur de processeur peut basculer entre les threads (le coût pour mettre en pause et reprendre un thread est d'environ 100 cycles d'horloge) tandis que le premier thread attend une entrée à latence élevée (quelque chose de plus cher que les registres (1 horloge) et la RAM (5 nanosecondes)). E / S disque, accès réseau (latence de 250 ms), lecture de données sur un CD ou un bus lent, ou appel à une base de données. Avoir plus de threads que de cœurs signifie qu'un travail utile peut être effectué pendant que les tâches à forte latence sont résolues.
Le processeur dispose d'un planificateur de threads qui attribue la priorité à chaque thread et permet à un thread de s'endormir, puis de reprendre après un temps prédéterminé. C'est le travail du programmateur de threads de réduire les battements, qui se produiraient si chaque thread n'exécutait que 100 instructions avant d'être à nouveau mis en veille. La surcharge des threads de commutation réduirait le débit utile total du cœur du processeur.
Pour cette raison, vous souhaiterez peut-être diviser votre problème en un nombre raisonnable de threads. Si vous écrivez du code pour effectuer une multiplication de matrice, la création d'un thread par cellule dans la matrice de sortie peut être excessive, alors qu'un thread par ligne ou par n lignes dans la matrice de sortie peut réduire les frais généraux de création, de mise en pause et de reprise des threads.
C'est aussi pourquoi la prédiction de branche est importante. Si vous avez une instruction if qui nécessite le chargement d'une valeur depuis la RAM mais que le corps des instructions if et else utilise des valeurs déjà chargées dans les registres, le processeur peut exécuter une ou les deux branches avant que la condition n'ait été évaluée. Une fois la condition revenue, le processeur appliquera le résultat de la branche correspondante et rejettera l'autre. Effectuer un travail potentiellement inutile ici est probablement mieux que de passer à un autre thread, ce qui pourrait conduire à des débordements.
Alors que nous sommes passés des processeurs monocœur à haute vitesse d'horloge aux processeurs multicœurs, la conception de la puce s'est concentrée sur le bourrage de plus de cœurs par matrice, l'amélioration du partage des ressources sur puce entre les cœurs, de meilleurs algorithmes de prédiction de branche, une meilleure surcharge de commutation de thread, et une meilleure planification des threads.
la source
La plupart des réponses ci-dessus parlent de performances et de fonctionnement simultané. Je vais aborder cela sous un angle différent.
Prenons le cas, disons, d'un programme d'émulation de terminal simpliste. Vous devez faire les choses suivantes:
(Les vrais émulateurs de terminal font plus, y compris potentiellement l'écho des éléments que vous tapez sur l'écran, mais nous passerons là-dessus pour le moment.)
Maintenant, la boucle de lecture à partir de la télécommande est simple, selon le pseudocode suivant:
La boucle de surveillance du clavier et d'envoi est également simple:
Le problème, cependant, est que vous devez le faire simultanément. Le code doit maintenant ressembler davantage à ceci si vous n'avez pas de thread:
La logique, même dans cet exemple délibérément simplifié qui ne prend pas en compte la complexité réelle des communications, est assez obscure. Avec le threading, cependant, même sur un seul noyau, les deux boucles de pseudo-code peuvent exister indépendamment sans entrelacer leur logique. Étant donné que les deux threads seront principalement liés aux E / S, ils ne mettent pas une charge lourde sur le processeur, même s'ils sont, à proprement parler, plus de gaspillage de ressources processeur que la boucle intégrée.
Maintenant, bien sûr, l'utilisation dans le monde réel est plus compliquée que ce qui précède. Mais la complexité de la boucle intégrée augmente de façon exponentielle à mesure que vous ajoutez plus de préoccupations à l'application. La logique est de plus en plus fragmentée et vous devez commencer à utiliser des techniques telles que les machines à états, les coroutines, etc. pour que les choses soient gérables. Gérable, mais non lisible. Le filetage garde le code plus lisible.
Alors pourquoi n'utiliseriez-vous pas le filetage?
Eh bien, si vos tâches sont liées au processeur au lieu des E / S, le threading ralentit en fait votre système. La performance en souffrira. Beaucoup, dans de nombreux cas. ("Thrashing" est un problème courant si vous supprimez trop de threads liés au processeur. Vous finissez par passer plus de temps à changer les threads actifs qu'à exécuter le contenu des threads eux-mêmes.) En outre, l'une des raisons pour lesquelles la logique ci-dessus est si simple est que j'ai choisi très délibérément un exemple simpliste (et irréaliste). Si vous vouliez faire écho à ce qui a été tapé à l'écran, vous avez un nouveau monde de blessures lorsque vous introduisez le verrouillage des ressources partagées. Avec une seule ressource partagée, ce n'est pas tellement un problème, mais cela commence à devenir un problème de plus en plus important car vous avez plus de ressources à partager.
Donc, en fin de compte, le filetage concerne beaucoup de choses. Par exemple, il s'agit de rendre les processus liés aux E / S plus réactifs (même s'ils sont globalement moins efficaces) comme certains l'ont déjà dit. Il s'agit également de rendre la logique plus facile à suivre (mais uniquement si vous minimisez l'état partagé). Il s'agit de beaucoup de choses et vous devez décider au cas par cas si ses avantages l'emportent sur ses inconvénients.
la source
Bien que vous puissiez certainement utiliser des threads pour accélérer les calculs en fonction de votre matériel, l'une de leurs principales utilisations est de faire plus d'une chose à la fois pour des raisons de convivialité.
Par exemple, si vous devez effectuer un traitement en arrière-plan tout en restant réactif aux entrées de l'interface utilisateur, vous pouvez utiliser des threads. Sans threads, l'interface utilisateur se bloquait à chaque fois que vous tentiez d'effectuer un traitement intensif.
Voir aussi cette question connexe: Utilisations pratiques des threads
la source
Je ne suis pas du tout d'accord avec l'affirmation de @ kyoryu selon laquelle le nombre idéal est un thread par CPU.
Pensez-y de cette façon: pourquoi avons-nous des systèmes d'exploitation multi-traitements? Pour la plupart de l'histoire de l'ordinateur, presque tous les ordinateurs avaient un processeur. Pourtant, à partir des années 1960, tous les «vrais» ordinateurs avaient des systèmes d'exploitation multi-traitements (ou multi-tâches).
Vous exécutez plusieurs programmes afin que l'un puisse s'exécuter tandis que d'autres sont bloqués pour des choses comme IO.
mettons de côté les arguments sur la question de savoir si les versions de Windows avant NT étaient multi-tâches. Depuis lors, tous les vrais OS avaient le multitâche. Certains ne l'exposent pas aux utilisateurs, mais c'est quand même là, faire des choses comme écouter la radio du téléphone portable, parler à la puce GPS, accepter l'entrée de la souris, etc.
Les threads ne sont que des tâches un peu plus efficaces. Il n'y a pas de différence fondamentale entre une tâche, un processus et un thread.
Un processeur est une chose terrible à gaspiller, alors ayez beaucoup de choses prêtes à l'utiliser quand vous le pouvez.
Je conviendrai qu'avec la plupart des langages procéduraux, C, C ++, Java, etc., écrire un code thread-safe approprié demande beaucoup de travail. Avec 6 processeurs cœurs sur le marché aujourd'hui et 16 processeurs cœurs non loin de là, je m'attends à ce que les gens s'éloignent de ces anciens langages, car le multi-threading est de plus en plus une exigence critique.
Le désaccord avec @kyoryu est juste à mon humble avis, le reste est un fait.
la source
Imaginez un serveur Web qui doit répondre à un nombre arbitraire de requêtes. Vous devez traiter les demandes en parallèle, car sinon chaque nouvelle demande doit attendre que toutes les autres demandes soient terminées (y compris l'envoi de la réponse sur Internet). Dans ce cas, la plupart des serveurs Web ont beaucoup moins de cœurs que le nombre de requêtes qu'ils traitent habituellement.
Cela facilite également la tâche du développeur du serveur: vous n'avez qu'à écrire un programme de thread qui sert une requête, vous n'avez pas à penser à stocker plusieurs requêtes, à l'ordre dans lequel vous les servez, etc.
la source
De nombreux threads seront endormis, attendant l'entrée de l'utilisateur, les E / S et d'autres événements.
la source
Les threads peuvent améliorer la réactivité des applications d'interface utilisateur. De plus, vous pouvez utiliser des threads pour optimiser le travail de vos cœurs. Par exemple, sur un seul cœur, vous pouvez avoir un thread effectuant les E / S et un autre effectuant des calculs. S'il s'agissait d'un seul thread, le cœur pourrait essentiellement être inactif en attendant la fin de l'E / S. C'est un exemple assez élevé, mais les threads peuvent certainement être utilisés pour marteler votre processeur un peu plus fort.
la source
Un processeur, ou CPU, est la puce physique qui est connectée au système. Un processeur peut avoir plusieurs cœurs (un cœur est la partie de la puce qui est capable d'exécuter des instructions). Un cœur peut apparaître pour le système d'exploitation comme plusieurs processeurs virtuels s'il est capable d'exécuter simultanément plusieurs threads (un thread est une seule séquence d'instructions).
Un processus est un autre nom pour une application. En général, les processus sont indépendants les uns des autres. Si un processus meurt, cela n'entraîne pas la mort d'un autre processus. Il est possible pour les processus de communiquer ou de partager des ressources telles que la mémoire ou les E / S.
Chaque processus dispose d'un espace d'adressage et d'une pile séparés. Un processus peut contenir plusieurs threads, chacun capable d'exécuter des instructions simultanément. Tous les threads d'un processus partagent le même espace d'adressage, mais chaque thread aura sa propre pile.
Espérons que ces définitions et d'autres recherches utilisant ces principes fondamentaux vous aideront à comprendre.
la source
L'utilisation idéale des threads est, en effet, un par cœur.
Cependant, à moins que vous n'utilisiez exclusivement des E / S asynchrones / non bloquantes, il y a de fortes chances que vous ayez des threads bloqués sur E / S à un moment donné, ce qui n'utilisera pas votre CPU.
En outre, les langages de programmation typiques rendent quelque peu difficile l'utilisation d'un thread par CPU. Les langages conçus autour de la concurrence (comme Erlang) peuvent faciliter l'utilisation de threads supplémentaires.
la source
De la façon dont certaines API sont conçues, vous n'avez pas d'autre choix que de les exécuter dans un thread séparé (tout ce qui a des opérations de blocage). Un exemple serait les bibliothèques HTTP de Python (AFAIK).
Habituellement, ce n'est pas vraiment un problème (si c'est un problème, le système d'exploitation ou l'API doit être livré avec un mode de fonctionnement asynchrone alternatif, c'est-à-dire:)
select(2)
, car cela signifie probablement que le thread va dormir pendant l'attente d'I / O achèvement. D'un autre côté, si quelque chose fait un calcul lourd, vous devez le mettre dans un thread séparé que, par exemple, le thread GUI (à moins que vous n'appréciez le multiplexage manuel).la source
Je sais que c'est une question très ancienne avec beaucoup de bonnes réponses, mais je suis ici pour souligner quelque chose qui est important dans l'environnement actuel:
Si vous souhaitez concevoir une application pour le multi-threading, vous ne devez pas concevoir pour un paramètre matériel spécifique. La technologie des processeurs progresse assez rapidement depuis des années et le nombre de cœurs augmente régulièrement. Si vous concevez délibérément votre application de telle sorte qu'elle n'utilise que 4 threads, vous vous restreignez potentiellement dans un système octa-core (par exemple). Maintenant, même les systèmes à 20 cœurs sont disponibles dans le commerce, donc une telle conception fait certainement plus de mal que de bien.
la source
En réponse à votre première conjecture: les machines multicœurs peuvent exécuter simultanément plusieurs processus, pas seulement les multiples threads d'un même processus.
En réponse à votre première question: le but de plusieurs threads est généralement d'effectuer simultanément plusieurs tâches dans une seule application. Les exemples classiques sur le net sont un programme de messagerie envoyant et recevant du courrier, et un serveur Web recevant et envoyant des requêtes de page. (Notez qu'il est essentiellement impossible de réduire un système comme Windows à n'exécuter qu'un seul thread ou même un seul processus. Exécutez le Gestionnaire des tâches de Windows et vous verrez généralement une longue liste de processus actifs, dont beaucoup exécuteront plusieurs threads. )
En réponse à votre deuxième question: la plupart des processus / threads ne sont pas liés au processeur (c'est-à-dire qu'ils ne fonctionnent pas de manière continue et ininterrompue), mais s'arrêtent et attendent fréquemment la fin des E / S. Pendant cette attente, d'autres processus / threads peuvent s'exécuter sans «voler» le code en attente (même sur une machine à un seul cœur).
la source
Un thread est une abstraction qui vous permet d'écrire du code aussi simple qu'une séquence d'opérations, ignorant parfaitement que le code est exécuté entrelacé avec un autre code, ou parqué en attente d'E / S, ou (peut-être un peu plus conscient) en attendant d'autres threads événements ou messages.
la source
Le fait est que la grande majorité des programmeurs ne comprennent pas comment concevoir une machine à états. Être capable de tout mettre dans son propre thread libère le programmeur d'avoir à réfléchir à la façon de représenter efficacement l'état des différents calculs en cours afin qu'ils puissent être interrompus et repris plus tard.
À titre d'exemple, considérons la compression vidéo, une tâche très gourmande en processeur. Si vous utilisez un outil d'interface graphique, vous souhaitez probablement que l'interface reste réactive (afficher la progression, répondre aux demandes d'annulation, redimensionner la fenêtre, etc.). Ainsi, vous concevez votre logiciel d'encodeur pour traiter une grande unité (une ou plusieurs images) à la fois et l'exécuter dans son propre thread, séparé de l'interface utilisateur.
Bien sûr, une fois que vous vous êtes rendu compte qu'il aurait été bien de pouvoir enregistrer l'état d'encodage en cours afin de pouvoir fermer le programme pour redémarrer ou jouer à un jeu gourmand en ressources, vous réalisez que vous auriez dû apprendre à concevoir des machines à états à partir du début. Soit cela, soit vous décidez de créer un tout nouveau problème d'hibernation des processus de votre système d'exploitation afin que vous puissiez suspendre et reprendre des applications individuelles sur le disque ...
la source