Bien que les threads puissent accélérer l'exécution du code, sont-ils réellement nécessaires? Est-ce que chaque morceau de code peut être fait en utilisant un seul thread ou existe-t-il quelque chose d'existant qui ne peut être accompli qu'en utilisant plusieurs threads?
programming-languages
multithreading
Oiseau en colère
la source
la source
Réponses:
Tout d'abord, les threads ne peuvent pas accélérer l'exécution du code. Ils ne rendent pas l'ordinateur plus rapide. Tout ce qu'ils peuvent faire, c'est augmenter l'efficacité de l'ordinateur en utilisant du temps qui serait autrement perdu. Dans certains types de traitement, cette optimisation peut augmenter l'efficacité et réduire le temps d'exécution.
La réponse simple est oui. Vous pouvez écrire n'importe quel code à exécuter sur un seul thread. Preuve: Un système à processeur unique ne peut exécuter des instructions que de manière linéaire. L'exécution de plusieurs lignes est effectuée par le système d'exploitation, qui enregistre les interruptions, enregistre l'état du thread en cours et en démarre un autre.
La réponse complexe est ... plus complexe! La raison pour laquelle les programmes multithread peuvent souvent être plus efficaces que les programmes linéaires est un "problème" matériel. La CPU peut exécuter des calculs plus rapidement que la mémoire et les entrées / sorties de stockage. Ainsi, une instruction "add", par exemple, s'exécute beaucoup plus rapidement qu'un "fetch". Les caches et l'extraction d'instructions de programme dédiées (pas sûr du terme exact ici) peuvent combattre cela dans une certaine mesure, mais le problème de la vitesse reste.
Le thread est un moyen de lutter contre cette non-concordance en utilisant le processeur pour obtenir des instructions liées au processeur pendant l'exécution des instructions d'entrée-sortie. Un plan d’exécution de threads typique serait probablement: récupérer des données, traiter des données, écrire des données. Supposons que l'extraction et l'écriture prennent 3 cycles et que le traitement en nécessite un, à des fins d'illustration. Vous pouvez voir que pendant que l'ordinateur lit ou écrit, il ne fait rien pendant 2 cycles chacun? Il est clair que nous sommes paresseux et nous devons craquer notre fouet d'optimisation!
Nous pouvons réécrire le processus en utilisant le threading pour utiliser ce temps perdu:
Etc. Évidemment, ceci est un exemple quelque peu artificiel, mais vous pouvez voir comment cette technique peut utiliser le temps qui serait autrement passé à attendre IO.
Notez que le threading présenté ci-dessus ne peut qu'accroître l'efficacité sur les processus fortement liés à l'IO. Si un programme calcule principalement des choses, il ne va pas y avoir beaucoup de "trous" dans lesquels nous pourrions faire plus de travail. En outre, il y a une surcharge de plusieurs instructions lors du changement de thread. Si vous exécutez trop de threads, le processeur passera la majeure partie de son temps à basculer et à ne pas trop travailler sur le problème. C'est ce qu'on appelle la raclée .
Tout cela va bien pour un processeur simple cœur, mais la plupart des processeurs modernes ont deux cœurs ou plus. Les threads ont toujours le même objectif - optimiser l'utilisation du processeur, mais cette fois, nous avons la possibilité d'exécuter deux instructions distinctes en même temps. Cela peut réduire le temps d'exécution d'un facteur égal au nombre de cœurs disponibles, car l'ordinateur est multitâche, et non un changement de contexte.
Avec plusieurs cœurs, les threads fournissent une méthode de division du travail entre les deux cœurs. Ce qui précède s'applique toujours à chaque noyau individuel; Un programme qui fonctionne au maximum avec deux threads sur un même cœur fonctionnera probablement avec une efficacité maximale avec environ quatre threads sur deux cœurs. (L'efficacité est mesurée ici par le nombre minimum d'exécutions d'instructions NOP.)
Les problèmes d'exécution de threads sur plusieurs cœurs (par opposition à un seul) sont généralement pris en charge par le matériel. Le CPU s'assurera qu'il verrouille les emplacements de mémoire appropriés avant de le lire / écrire. (J'ai lu qu'il utilise un bit de drapeau spécial en mémoire pour cela, mais cela peut être accompli de plusieurs façons.) En tant que programmeur utilisant des langages de niveau supérieur, vous n'avez plus à vous soucier de quoi que ce soit sur deux cœurs. aurait à un.
TL; DR: les threads peuvent fractionner le travail pour permettre à l'ordinateur de traiter plusieurs tâches de manière asynchrone. Cela permet à l'ordinateur de fonctionner avec une efficacité maximale en utilisant tout le temps de traitement disponible, plutôt que de le verrouiller lorsqu'un processus attend une ressource.
la source
Rien.
Esquisse d'épreuve simple:
Notez, cependant, qu'il y a une grosse hypothèse cachée dedans: à savoir que le langage utilisé dans le seul thread est Turing-complete.
Donc, la question plus intéressante serait la suivante: « Peut ajouter que le multi-threading à un langage complet non Turing faire Turing-complet? » Et je crois que la réponse est "oui".
Prenons Total des langages fonctionnels. [Pour ceux qui ne sont pas familiers: tout comme la programmation fonctionnelle consiste à programmer avec des fonctions, la programmation fonctionnelle totale consiste à programmer avec des fonctions totales.]
Le total des langages fonctionnels n'est évidemment pas complet avec Turing: vous ne pouvez pas écrire une boucle infinie dans un TFPL (en fait, c'est à peu près la définition de "total"), mais vous pouvez le faire dans une machine de Turing, car il existe au moins un programme qui ne peut pas être écrit dans un TFPL mais peut le être dans un UTM. Par conséquent, les TFPL sont moins puissants en calcul que les UTM.
Cependant, dès que vous ajoutez des threads à un TFPL, vous obtenez des boucles infinies: faites juste chaque itération de la boucle dans un nouveau thread. Chaque thread individuel renvoie toujours un résultat. Il s'agit donc de Total, mais chaque thread génère également un nouveau thread qui exécute la prochaine itération, à l'infini.
Je pense que cette langue serait Turing-complète.
À tout le moins, cela répond à la question initiale:
Si vous avez un langage qui ne peut pas faire de boucles infinies, le multi-threading vous permet de faire des boucles infinies.
Notez, bien sûr, que la création d'un fil est un effet secondaire et que notre langage étendu n'est donc pas seulement Total, il n'est même plus fonctionnel.
la source
En théorie, tout ce qu'un programme multithread peut faire peut également être fait avec un programme à un seul thread, mais juste plus lentement.
En pratique, la différence de vitesse peut être si grande qu'il est impossible d'utiliser un programme à un seul thread pour la tâche. Par exemple, si un travail de traitement de données par lots est exécuté toutes les nuits et qu'il faut plus de 24 heures pour terminer un seul thread, vous n'avez pas d'autre choix que de le rendre multithread. (En pratique, le seuil est probablement encore plus bas: souvent, ces tâches de mise à jour doivent être terminées tôt le matin, avant que les utilisateurs ne puissent recommencer à utiliser le système. En outre, d'autres tâches peuvent dépendre d'elles, qui doivent également s'achever dans la même nuit. La durée d’utilisation disponible peut être aussi basse que quelques heures / minutes.)
Faire du travail informatique sur plusieurs threads est une forme de traitement distribué; vous distribuez le travail sur plusieurs threads. L'économiseur d'écran SETI est un autre exemple de traitement distribué (utilisant plusieurs ordinateurs au lieu de plusieurs threads): il faudrait beaucoup de temps pour traiter autant de données de mesure sur un seul processeur et les chercheurs préféreraient voir les résultats avant la retraite ;-) ne disposant pas du budget nécessaire pour louer un superordinateur aussi longtemps, ils répartissent le travail sur des millions d'ordinateurs personnels pour le rendre moins cher.
la source
L'utilisation de threads offre certains avantages en termes de performances, car elle permet de répartir le travail sur plusieurs cœurs, mais leur prix est souvent très avantageux.
L’un des inconvénients de l’utilisation de threads qui n’a pas encore été mentionnée est la perte de compartimentalisation des ressources que vous obtenez avec des espaces de processus à thread unique. Par exemple, supposons que vous rencontriez le cas d'une erreur de segmentation. Dans certains cas, il est possible de remédier à cette situation dans une application multi-processus, en laissant simplement l'enfant défaillant mourir et en en reproduisant un nouveau. C'est le cas dans le backend prefork d'Apache. Quand une instance httpd monte en panne, le pire des cas est que la requête HTTP particulière peut être abandonnée pour ce processus, mais Apache génère un nouvel enfant et souvent la requête si elle est simplement renvoyée et traitée. Le résultat final est que Apache dans son ensemble n'est pas détruit avec le thread défectueux.
Une autre considération dans ce scénario concerne les fuites de mémoire. Il existe des cas où vous pouvez gérer avec élégance une panne de thread (sous UNIX, la récupération de signaux spécifiques - même segfault / fpviolation - est possible), mais même dans ce cas, vous avez peut-être perdu toute la mémoire allouée par ce thread. (malloc, nouveau, etc.). Ainsi, même si votre processus est susceptible de continuer à fonctionner, il perd de plus en plus de mémoire avec chaque panne / récupération. Là encore, il existe, dans une certaine mesure, des moyens de minimiser ce problème, comme l'utilisation par Apache de pools de mémoire. Mais cela ne protège toujours pas contre la mémoire qui aurait pu être allouée par des bibliothèques tierces que le thread aurait pu utiliser.
Et, comme l'ont souligné certaines personnes, la compréhension des primitives de synchronisation est peut-être la chose la plus difficile à faire. Ce problème en lui-même - obtenir juste la logique générale correcte pour tout votre code - peut être un casse-tête énorme. De mystérieuses impasses sont susceptibles de se produire aux moments les plus étranges, et parfois même avant que votre programme n'ait été exécuté en production, ce qui rend le débogage d'autant plus difficile. Ajoutez à cela le fait que les primitives de synchronisation varient souvent beaucoup avec la plate-forme (Windows contre POSIX), et que le débogage est souvent plus difficile, ainsi que la possibilité de conditions de concurrence à tout moment (démarrage / initialisation, exécution et arrêt), la programmation avec des threads a vraiment peu de pitié pour les débutants. Et même pour les experts, il y a toujours peu de pitié simplement parce que la connaissance du filetage lui-même ne minimise pas la complexité en général. Chaque ligne de code threadé semble parfois aggraver de manière exponentielle la complexité globale du programme et augmenter la probabilité qu'une impasse cachée ou une situation de concurrence étrange apparaisse à tout moment. Il peut également être très difficile d’écrire des cas de test pour dénicher ces choses.
C'est pourquoi certains projets tels qu'Apache et PostgreSQL sont pour la plupart basés sur des processus. PostgreSQL exécute tous les threads dans un processus séparé. Bien sûr, cela ne résout pas le problème de la synchronisation et des conditions de concurrence, mais cela ajoute un peu de protection et simplifie à certains égards les choses.
Plusieurs processus exécutant chacun un seul thread d'exécution peuvent être bien meilleurs que plusieurs threads exécutés dans un seul processus. Et avec l'avènement de la plupart des nouveaux codes peer-to-peer tels qu'AMQP (RabbitMQ, Qpid, etc.) et ZeroMQ, il est beaucoup plus facile de scinder les threads entre différents espaces de processus et même des machines et des réseaux, ce qui simplifie grandement les choses. Mais quand même, ce n'est pas une solution miracle. Il reste encore une complexité à gérer. Vous déplacez simplement certaines de vos variables de l'espace de processus vers le réseau.
L'essentiel est que la décision d'entrer dans le domaine des threads n'est pas légère. Une fois que vous pénétrez sur ce territoire, presque instantanément, tout devient plus complexe et de nouvelles races de problèmes entrent dans votre vie. Cela peut être amusant et cool, mais c'est comme l'énergie nucléaire: quand les choses tournent mal, elles peuvent aller mal et vite. Je me souviens d'avoir suivi un cours de formation à la criticité il y a de nombreuses années et ils ont montré des photos de scientifiques de Los Alamos qui ont joué avec du plutonium dans les laboratoires de la Seconde Guerre mondiale. Beaucoup prenaient peu ou pas de précautions à prendre en cas d’exposition, et en un clin d’œil - en un éclair, une clarté indolore, tout serait fini pour eux. Quelques jours plus tard, ils étaient morts. Richard Feynman a plus tard qualifié cela de " chatouillant la queue du dragon""C’est un peu ce que peut être le jeu avec les discussions (du moins pour moi en tout cas). Cela semble plutôt inoffensif au début, et quand vous mordez, vous vous grattez la tête à la rapidité avec laquelle les choses se sont détériorées. Mais au moins les discussions ont gagné. ne te tue pas.
la source
Tout d'abord, une application à thread unique ne tirera jamais parti d'un processeur multicœur ou d'un hyper-threading. Cependant, même sur un seul cœur, les processeurs mono-thread faisant du multi-threading présentent des avantages.
Considérez l'alternative et si cela vous rend heureux. Supposons que plusieurs tâches doivent être exécutées simultanément. Par exemple, vous devez continuer à communiquer avec deux systèmes différents. Comment faites-vous cela sans multi-threading? Vous créerez probablement votre propre planificateur et le laisserez appeler les différentes tâches à exécuter. Cela signifie que vous devez diviser vos tâches en plusieurs parties. Vous devez probablement respecter certaines contraintes en temps réel et veiller à ce que vos pièces ne prennent pas trop de temps. Sinon, la minuterie expirera dans d'autres tâches. Cela rend plus difficile le fractionnement d’une tâche. Plus vous avez de tâches à gérer, plus vous devez vous séparer et plus votre ordonnanceur sera complexe pour répondre à toutes les contraintes.
Lorsque vous avez plusieurs threads, la vie peut devenir plus facile. Un planificateur préemptif peut arrêter un thread à tout moment, conserver son état et en redémarrer un autre. Il redémarrera lorsque votre thread aura son tour. Avantages: la complexité de l'écriture d'un planificateur a déjà été effectuée pour vous et vous n'avez pas à diviser vos tâches. En outre, le planificateur est capable de gérer des processus / threads dont vous n'êtes même pas au courant. De plus, lorsqu'un thread n'a rien à faire (il attend un événement), il ne prend aucun cycle de traitement. Ce n'est pas si facile à accomplir lorsque vous créez votre planificateur à thread unique. (endormir quelque chose n'est pas si difficile, mais comment se réveille-t-il?)
L'inconvénient du développement multithread est que vous devez comprendre les problèmes de concurrence, les stratégies de verrouillage, etc. Développer du code multithread sans erreur peut être assez difficile. Et le débogage peut être encore plus difficile.
la source
Oui. Vous ne pouvez pas exécuter de code sur plusieurs processeurs ou cœurs de processeur avec un seul thread.
Sans plusieurs processeurs / cœurs, les threads peuvent toujours simplifier le code conceptuellement exécuté en parallèle, tel que la gestion des clients sur un serveur - mais vous pouvez faire la même chose sans threads.
la source
Les threads ne sont pas seulement une question de vitesse, mais de concurrence.
Si vous ne proposez pas une application par lots comme suggéré par @Peter, mais plutôt un toolkit à interface graphique tel que WPF, comment interagir avec les utilisateurs et la logique métier avec un seul thread?
En outre, supposons que vous construisiez un serveur Web. Comment serviriez-vous plusieurs utilisateurs simultanément avec un seul thread (sans autre processus)?
Il existe de nombreux scénarios dans lesquels un seul thread simple ne suffit pas. C'est pourquoi les avancées récentes telles que le processeur Intel MIC avec plus de 50 cœurs et des centaines de threads ont lieu.
Oui, la programmation parallèle et concurrente est difficile. Mais nécessaire.
la source
Le multi-threading peut permettre à l'interface graphique de continuer à répondre aux opérations de traitement longues. Sans multi-threading, l'utilisateur resterait bloqué à regarder un formulaire verrouillé pendant l'exécution d'un long processus.
la source
Le code multithread peut bloquer la logique du programme et accéder aux données obsolètes de la même manière que les threads simples.
Les threads peuvent prendre un bogue obscur de quelque chose qu'un programmeur moyen peut s’attendre à déboguer et à le déplacer dans le royaume où des histoires sont racontées sur la chance nécessaire bon moment.
la source
les applications traitant du blocage d'E / S qui doivent également rester sensibles aux autres entrées (l'interface graphique ou d'autres connexions) ne peuvent pas être mises en file indienne
l'ajout de méthodes de vérification dans la bibliothèque d'IO pour voir combien de choses peuvent être lues sans blocage peut aider cela, mais peu de bibliothèques donnent des garanties complètes à ce sujet.
la source
Beaucoup de bonnes réponses, mais je ne suis pas sûr que les phrases soient aussi précises que je le voudrais - cela offre peut-être une autre façon de voir les choses:
Les threads ne sont qu'une simplification de la programmation, comme les objets, les acteurs ou les boucles for (Oui, tout ce que vous implémentez avec des boucles que vous pouvez implémenter avec if / goto).
Sans thread, vous implémentez simplement un moteur d'état. J'ai eu à le faire plusieurs fois (la première fois que je l'avais fait, je n'en avais jamais entendu parler - j'ai simplement fait une grosse déclaration de commutateur contrôlée par une variable "State"). Les machines à états sont encore courantes mais peuvent être ennuyeuses. Avec les fils, un énorme morceau du passe-partout s'en va.
Ils facilitent également pour un langage la possibilité de décomposer son exécution à l’aide de plusieurs processeurs conviviaux (de même que les acteurs, je crois).
Java fournit des threads "verts" sur les systèmes où le système d'exploitation ne fournit AUCUN support de threading. Dans ce cas, il est plus facile de voir qu’ils ne sont clairement rien de plus qu’une abstraction de programmation.
la source
Les systèmes d'exploitation utilisent un concept de découpage temporel dans lequel chaque thread obtient son temps d'exécution puis est préempté. Une telle approche peut remplacer le thread tel qu’il est actuellement, mais écrire vos propres planificateurs dans chaque application serait excessif. De plus, vous devrez travailler avec des périphériques d’entrée / sortie, etc. Et aurait besoin d’un soutien du côté matériel, de sorte que vous puissiez déclencher des interruptions pour que votre planificateur soit exécuté. Fondamentalement, vous écririez un nouveau système d'exploitation à chaque fois.
En général, le threading peut améliorer les performances dans les cas où les threads attendent les entrées / sorties ou sont en veille. Il vous permet également de créer des interfaces réactives et d’arrêter des processus tout en effectuant de longues tâches. Et aussi, le threading améliore les choses sur les vrais processeurs multicœurs.
la source
Tout d'abord, les threads peuvent faire deux choses en même temps (si vous avez plus d'un coeur). Bien que vous puissiez également le faire avec plusieurs processus, certaines tâches ne sont tout simplement pas bien réparties entre plusieurs processus.
En outre, certaines tâches comportent des espaces que vous ne pouvez pas éviter facilement. Par exemple, il est difficile de lire les données d'un fichier sur un disque et de demander à votre processus de faire autre chose en même temps. Si votre tâche nécessite nécessairement beaucoup de lecture de données sur le disque, votre processus passera beaucoup de temps à attendre le disque, peu importe ce que vous ferez.
Deuxièmement, les threads peuvent vous éviter d’optimiser de grandes quantités de code qui ne sont pas critiques en termes de performances. Si vous n'avez qu'un seul thread, chaque élément de code est critique en termes de performances. Si cela bloque, vous êtes submergé - aucune tâche qui serait accomplie par ce processus ne peut progresser. Avec les threads, un bloc n'affectera que ce thread et d'autres threads peuvent venir et travailler sur des tâches qui doivent être effectuées par ce processus.
Un bon exemple est le code de traitement des erreurs rarement exécuté. Supposons qu'une tâche rencontre une erreur très peu fréquente et que le code permettant de gérer cette erreur doive être mis en page en mémoire. Si le disque est occupé et que le processus ne comporte qu'un seul thread, vous ne pouvez pas avancer jusqu'à ce que le code permettant de gérer cette erreur puisse être chargé en mémoire. Cela peut provoquer une réponse en rafale.
Un autre exemple est si vous avez très rarement à faire une recherche de base de données. Si vous attendez que la base de données réponde, votre code prendra un énorme retard. Mais vous ne voulez pas vous donner la peine de rendre tout ce code asynchrone, car il est si rare que vous ayez besoin de faire ces recherches. Avec un fil pour faire ce travail, vous obtenez le meilleur des deux mondes. Un thread pour effectuer ce travail le rend non critique comme il se doit.
la source