web-dev-qa-db-fra.com

Multithreading: Quel est l'intérêt de plus de threads que de cœurs?

Je pensais que l’intérêt d’un ordinateur multicœur était de pouvoir exécuter simultanément plusieurs threads. Dans ce cas, si vous avez une machine quad-core, à quoi servirait-il d'avoir plus de 4 threads exécutés à la fois? Ne seraient-ils pas simplement en train de voler du temps l'un à l'autre?

97
Nick Heiner

La réponse concerne le rôle des threads, à savoir le parallélisme: exécuter plusieurs lignes d'exécution à la fois. Dans un système «idéal», vous auriez un thread exécutant par cœur: aucune interruption. En réalité ce n'est pas le cas. Même si vous avez quatre cœurs et quatre threads fonctionnels, votre processus et ses threads seront constamment remplacés par d'autres processus et threads. Si vous utilisez 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 ne rencontrerez jamais une situation dans laquelle un thread s'exécutera sans que le temps ne lui soit "volé". (Eh bien, vous pourriez si 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.)

La réponse à cette question est oui: plus de quatre threads sur une vraie machine à quatre cœurs peuvent vous donner une situation dans laquelle ils se "volent du temps", mais uniquement si chaque thread 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 qu'un thread effectue une petite quantité de travail ou attend autre chose), un autre thread en cours de planification est en fait une bonne situation.

C'est en fait plus compliqué que ça:

  • Que se passe-t-il si vous avez cinq tâches à effectuer en même temps? Il est plus logique de les exécuter tous en même temps que d’en exécuter quatre, puis 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 potentiellement perdre du temps à attendre sans rien faire d'utile. C'est une situation très commune.

  • Si vous avez du travail à exécuter, un mécanisme commun consiste à utiliser un pool de threads. Il peut sembler judicieux d’avoir le même nombre de threads que de cœurs, mais le pool de threads .Net a jusqu’à 250 threads disponibles par processeur . Je ne suis pas certain de la raison pour laquelle ils le font, mais je suppose que la taille des tâches qui leur sont confiées est exécutée sur les threads.

Donc: voler du temps n'est pas une mauvaise chose (et ce n'est pas non plus un vol: c'est ainsi que le système est censé fonctionner.) Ecrivez vos programmes multithreads en fonction du type de travail que feront les threads, ce 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, si votre programme n’est pas vraiment critique en termes de performances, ne vous inquiétez pas trop :)

54
David

Ce n’est pas toujours parce qu’un fil existe qu’il fonctionne activement. De nombreuses applications de threads impliquent que certains d'entre eux soient mis en veille jusqu'à ce qu'il soit temps d'agir - par exemple, une entrée utilisateur déclenchant le réveil des threads, leur traitement et leur retour en veille.

Les threads sont essentiellement des tâches individuelles pouvant fonctionner indépendamment les uns des autres, sans qu'il soit nécessaire de se tenir au courant de la progression d'une autre tâche. Il est tout à fait possible d’avoir plus d’entre eux que de pouvoir courir simultanément; ils restent utiles même s'ils doivent parfois faire la queue l'un derrière l'autre.

49
Amber

Le fait est que, malgré le fait que le nombre de threads ne soit pas réellement accéléré, vous pouvez utiliser des threads pour démêler des éléments de logique qui ne devraient pas nécessairement être interdépendants. 

Même dans une application moyennement complexe, l'utilisation d'un seul thread essaye de tout faire rapidement pour rendre le "flux" de votre code plus rapide. L'unique thread passe le plus clair de son temps à interroger ceci, à vérifier, en appelant conditionnellement les routines si nécessaire, et il devient difficile de voir autre chose qu'un fouillis de minuties.

Comparez cela au cas où vous pouvez dédier des threads à des tâches afin que, en regardant un thread individuel, vous puissiez voir ce que fait ce thread. Par exemple, un thread peut bloquer l'attente d'une entrée depuis un 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'eux présentera un flux propre et déterminé, sans avoir à vérifier explicitement 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 l’action à suivre avec le processeur, de sorte que vous n’avez pas à effectuer de vérifications conditionnelles explicites partout dans votre application pour savoir ce qui pourrait bloquer et ce qui est prêt à être traité.

24
JustJeff

Si un thread attend une ressource (comme le chargement d'une valeur de RAM dans un registre, l'entrée/la sortie disque, l'accès réseau, le lancement d'un nouveau processus, l'interrogation d'une base de données ou l'attente d'une entrée utilisateur), le processeur peut travailler sur un autre thread et revenir au premier thread lorsque la ressource est disponible. Cela réduit le temps d'inactivité du processeur, car il peut effectuer des millions d'opérations au lieu de rester inactif.

Prenons l'exemple d'un thread qui doit lire les données d'un disque dur ..__ En 2014, un cœur de processeur typique fonctionne à 2,5 GHz et peut donc 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 de disque dur mécaniques typiques 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. Les performances des disques durs avec un cache réduit (mémoire tampon de 4 Mo) et des disques hybrides avec quelques Go de stockage peuvent être significatives, car la latence des données pour les lectures séquentielles ou à partir de la section hybride peut être de plusieurs ordres de grandeur plus rapide.

Un cœur de processeur peut basculer entre les threads (le coût de la pause et de la reprise d’un thread est d’environ 100 cycles d'horloge), tandis que le premier thread attend une entrée à latence élevée (rien de plus coûteux que les registres (1 horloge) et RAM (5 nanosecondes). )) Il s’agit notamment des entrées/sorties sur disque, des accès réseau (latence de 250 ms), de la lecture de données sur un CD ou un bus lent, ou d’un appel de base de données. Avoir plus de threads que de cœurs signifie qu'un travail utile peut être effectué pendant la résolution des tâches à latence élevée.

La CPU dispose d'un planificateur de threads qui attribue une priorité à chaque thread et permet à un thread de s'endormir, puis de le reprendre après un temps prédéterminé. C’est le travail du planificateur de threads de réduire les thrashs, ce qui se produirait si chaque thread n’exécutait que 100 instructions avant d’être de 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 la multiplication de matrice, créer un thread par cellule dans la matrice en sortie peut s'avérer excessif, tandis qu'un thread par ligne ou par rangée n dans la matrice en sortie peut réduire les coûts indirects liés à la création, à la mise en pause, et reprendre les discussions.

C'est aussi pourquoi la prédiction de branche est importante. Si vous avez une instruction if nécessitant le chargement d'une valeur à partir de RAM mais que le corps des instructions if et else utilise des valeurs déjà chargées dans des registres, le processeur peut exécuter une ou les deux branches avant l'évaluation de la condition. Une fois que la condition est revenue, le processeur appliquera le résultat de la branche correspondante et éliminera l’autre. Effectuer un travail potentiellement inutile ici est probablement mieux que de passer à un autre thread, ce qui pourrait conduire à des contusions.

Alors que nous passions de processeurs monocœurs à haute vitesse d'horloge à des processeurs multicœurs, la conception de puces s'est concentrée sur le bourrage de plus de cœurs par matrice, l'amélioration du partage des ressources sur la puce entre les cœurs, de meilleurs algorithmes de prédiction de branche, une charge de commutation de threads plus importante, et une meilleure planification des threads.

8
IceArdor

Je suis fortement en désaccord avec l'affirmation de @ kyoryu selon laquelle le nombre idéal est un thread par processeur.

Pensez-y de cette façon: pourquoi avons-nous des systèmes d’exploitation multitraitement? Pour la majeure partie de l'histoire de l'ordinateur, presque tous les ordinateurs avaient un processeur. Pourtant, à partir des années 1960, tous les ordinateurs "réels" disposaient d'un système d'exploitation à traitements multiples (ou multi-tâches). 

Vous exécutez plusieurs programmes pour pouvoir en exécuter un tandis que d'autres sont bloqués pour des choses comme IO.

permet de mettre de côté des arguments pour savoir si les versions de Windows antérieures à NT étaient multitâches. Depuis lors, tous les systèmes d’exploitation réels étaient multitâches. Certains ne l'exposent pas aux utilisateurs, mais il y fait quand même des choses, comme écouter la radio d'un téléphone portable, parler à la puce GPS, accepter une entrée de 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 à perdre, alors ayez beaucoup de choses prêtes à l’utiliser quand vous le pourrez.

Je conviens qu'avec la plupart des langages procéduraux, C, C++, Java, etc., écrire du code thread-safe approprié représente beaucoup de travail. Aujourd'hui, avec 6 processeurs sur le marché et 16 processeurs à proximité, je m'attends à ce que les gens s'éloignent de ces anciennes langues, car le multi-threading est de plus en plus essentiel.

Désaccord avec @kyoryu est juste à mon humble avis, le reste est un fait.

6
fishtoprecords

Bien que vous puissiez 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 certains traitements en arrière-plan et que vous souhaitez également rester sensible aux entrées de l'interface utilisateur, vous pouvez utiliser des threads. Sans threads, l'interface utilisateur se bloquerait à chaque fois que vous tenteriez d'effectuer un traitement lourd.

Voir également cette question connexe: Utilisations pratiques des threads

5
Cam

Imaginez un serveur Web devant servir un nombre arbitraire de demandes. 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 par Internet). Dans ce cas, la plupart des serveurs Web ont beaucoup moins de cœurs que le nombre de demandes qu’ils servent habituellement.

Cela facilite également la tâche du développeur du serveur: vous n’avez qu’à écrire un programme de threads qui sert une demande, vous n’aurez pas à penser au stockage de plusieurs demandes, à l’ordre dans lequel vous les envoyez, etc.

5
tobiw

La plupart des réponses ci-dessus parlent de performance et de fonctionnement simultané. Je vais aborder cela sous un angle différent.

Prenons le cas d'un programme d'émulation de terminal simpliste, par exemple. Vous devez faire les choses suivantes:

  • surveillez les caractères entrants du système distant et affichez-les
  • surveillez les choses venant du clavier et envoyez-les au système distant

(Les émulateurs de terminaux réels en font plus, y compris en faisant éventuellement écho aux éléments que vous tapez sur l'écran, mais nous allons laisser tomber cela pour le moment.)

Maintenant, la boucle pour lire à partir de la télécommande est simple, selon le pseudocode suivant:

while get-character-from-remote:
    print-to-screen character

La boucle de contrôle du clavier et d’envoi est également simple:

while get-character-from-keyboard:
    send-to-remote character

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:

loop:
    check-for-remote-character
    if remote-character-is-ready:
        print-to-screen character
    check-for-keyboard-entry
    if keyboard-is-ready:
        send-to-remote character

La logique, même dans cet exemple délibérément simplifié qui ne prend pas en compte la complexité des communications dans le monde réel, est assez obscurcie. Avec le threading, cependant, même sur un seul noyau, les deux boucles de pseudocode peuvent exister indépendamment sans entrelacer leur logique. Étant donné que les deux threads seront principalement liés aux E/S, ils ne surchargent pas le processeur, même s'ils sont, à proprement parler, plus de gaspillage de ressources de processeur que ne le serait la boucle intégrée.

Bien entendu, 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 de nouvelles préoccupations à l'application. La logique devient de plus en plus fragmentée et vous devez commencer à utiliser des techniques telles que les machines à états, les routines, etc. pour que les choses soient gérables. Gérable, mais pas lisible. Les threads rendent le code plus lisible.

Alors, pourquoi ne pas utiliser le filetage?

Eh bien, si vos tâches sont liées au processeur et non aux E/S, le threading ralentit votre système. La performance va en souffrir. Beaucoup, dans de nombreux cas. ("Thrashing" est un problème courant si vous supprimez trop de threads liés au processeur. Vous passez plus de temps à changer les threads actifs qu'à exécuter le contenu des threads eux-mêmes.) L'une des raisons pour lesquelles la logique ci-dessus est Si simple, c'est que j'ai délibérément choisi un exemple simpliste (et irréaliste). Si vous voulez faire écho à ce qui a été tapé à l'écran, vous créez un nouveau monde de blessures en introduisant le verrouillage des ressources partagées. Avec une seule ressource partagée, ce n'est pas vraiment un problème, mais cela commence à devenir un problème de plus en plus grand à mesure que vous avez plus de ressources à partager.

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é). Cela concerne beaucoup de choses, et vous devez décider si ses avantages l'emportent sur ses inconvénients au cas par cas.

De nombreux threads seront endormis, attendant l'entrée de l'utilisateur, les E/S et d'autres événements.

3
Puppy

Les threads peuvent aider à la réactivité dans les applications d'interface utilisateur. De plus, vous pouvez utiliser des threads pour obtenir plus de travail de vos cœurs. Par exemple, sur un seul cœur, vous pouvez avoir un thread exécutant IO et un autre effectuant des calculs. S'il s'agissait d'un seul thread, le noyau pourrait être essentiellement inactif en attendant que le IO soit terminé. C'est un bon exemple, mais les threads peuvent certainement être utilisés pour marteler un peu plus votre processeur.

2
Anon

Un processeur, ou CPU, est la puce physique connectée au système. Un processeur peut avoir plusieurs cœurs (un cœur est la partie de la puce capable d'exécuter des instructions). Un noyau peut apparaître au système d'exploitation comme plusieurs processeurs virtuels s'il est capable d'exécuter simultanément plusieurs threads (un thread est une séquence d'instructions unique).

Un processus est un autre nom pour une application. Généralement, les processus sont indépendants les uns des autres. Si un processus meurt, cela ne provoque pas la mort d'un autre processus. Il est possible que les processus communiquent ou partagent des ressources telles que la mémoire ou les E/S.

Chaque processus a un espace d'adressage et une pile distincts. Un processus peut contenir plusieurs threads, chacun pouvant 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 à mieux comprendre.

2
Srikar Doddi

De la manière dont certaines API sont conçues, vous n'avez aucun choix mais vous devez les exécuter dans un thread séparé (tout ce qui comporte des opérations de blocage). Un exemple serait les bibliothèques HTTP de Python (AFAIK).

Cela n’est cependant généralement pas un problème (le cas échéant, le système d’exploitation ou l’API doit être fourni avec un autre mode de fonctionnement asynchrone, à savoir: select(2)), car cela signifie probablement que le thread sera en veille pendant l’attente./O achèvement. D'autre part, si quelque chose effectue un calcul lourd, vous devez le mettre dans un thread distinct de celui de GUI (à moins que vous appréciiez le multiplexage manuel).

L'utilisation idéale des threads est en effet d'une par cœur.

Toutefois, sauf si vous utilisez exclusivement des E/S asynchrones/non bloquantes, il est fort probable que des threads soient bloqués à un moment donné sur IO, ce qui n'utilisera pas votre CPU.

De plus, les langages de programmation typiques rendent difficile l'utilisation d'un thread par CPU. Les langues conçues autour de la simultanéité (telle que Erlang) peuvent permettre de ne pas utiliser de threads supplémentaires.

1
kyoryu

Je sais que c'est une très vieille question avec beaucoup de bonnes réponses, mais je suis ici pour souligner quelque chose d'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 du processeur évolue assez rapidement depuis des années et le nombre de noyaux 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 plus de mal que de bien.

0
Jai

En réponse à votre première hypothèse: 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: l'intérêt de plusieurs threads consiste généralement à effectuer simultanément plusieurs tâches au sein d'une même application. Les exemples classiques sur le net sont un programme de messagerie permettant d'envoyer et de recevoir du courrier et un serveur Web recevant et envoyant des demandes de page. (Notez qu’il est pratiquement impossible de réduire un système tel que Windows à l’exécution d’un seul thread, voire d’un seul processus. Exécutez le Gestionnaire des tâches Windows pour afficher 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 s'exécutent 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 seule machine principale).

0
joe snyder