web-dev-qa-db-fra.com

Quelle est l'importance du multithreading dans l'industrie logicielle actuelle?

J'ai près de 3 ans d'expérience dans l'écriture d'applications Web en Java en utilisant des frameworks MVC (comme des struts). Je n'ai jamais écrit de code multithread jusqu'à présent bien que j'aie écrit du code pour les principales chaînes de vente au détail.

Je reçois quelques questions sur le multithreading lors des entretiens et j'y réponds généralement (surtout des questions simples). Cela m'a laissé me demander à quel point le multithreading est important dans le scénario actuel de l'industrie?

59
user2434

C'est extrêmement important.

Ce qui est plus important cependant, c'est de comprendre que le multithreading n'est qu'une façon de résoudre le problème d'asynchronie. L'environnement technique dans lequel de nombreuses personnes écrivent actuellement des logiciels diffère de l'environnement de développement de logiciels historique (d'applications monolithiques effectuant des calculs par lots) de deux manières principales:

  • Les machines à plusieurs cœurs sont désormais courantes. Nous ne pouvons plus nous attendre à ce que les vitesses d'horloge ou les densités de transistor augmentent de plusieurs ordres de grandeur. Le prix du calcul continuera de baisser, mais il baissera à cause de beaucoup de parallélisme. Nous allons devoir trouver un moyen de tirer parti de ce pouvoir.

  • Les ordinateurs sont désormais fortement mis en réseau et les applications modernes dépendent de la possibilité de récupérer des informations riches à partir de diverses sources.

D'un point de vue informatique, ces deux facteurs se résument essentiellement à la même idée centrale: les informations seront de plus en plus disponibles de manière asynchrone. Que les informations dont vous avez besoin soient calculées sur une autre puce de votre machine ou sur une puce à l'autre bout du monde, cela n'a pas vraiment d'importance. Quoi qu'il en soit, votre processeur est assis là à brûler des milliards de cycles par seconde en attente d'informations alors qu'il pourrait faire un travail utile.

Donc, ce qui compte maintenant, et ce qui importera encore plus à l'avenir, n'est pas multithreading en soi, mais plutôt traitant de l'asynchronie. Le multithreading n'est qu'une façon de le faire - une manière compliquée et sujette aux erreurs qui ne fera que devenir plus compliquée et plus sujette aux erreurs à mesure que les puces de modèle à mémoire faible deviennent plus largement utilisées.

Le défi pour les fournisseurs d'outils est de trouver un moyen mieux que le multithreading pour nos clients pour gérer l'infrastructure asynchrone qu'ils utiliseront à l'avenir.

92
Eric Lippert

Cela devient de plus en plus important car les processeurs modernes ont de plus en plus de cœurs. Il y a dix ans, la plupart des ordinateurs existants ne disposaient que d'un seul processeur, le multithreading n'était donc important que sur les applications serveur haut de gamme. De nos jours, même les ordinateurs portables de base ont des processeurs multicœurs. Dans quelques années, même les appareils mobiles ... Il faut donc de plus en plus de code pour utiliser les avantages de performance potentiels de la concurrence et pour fonctionner correctement dans un environnement multithread.

46
Péter Török

En général, le multi-threading est déjà assez important, et ne va devenir plus important que dans les prochaines années (comme l'a souligné Péter Török) - c'est ainsi que les processeurs évolueront dans un avenir prévisible (plus de cœurs au lieu de MHz plus élevé) .

Dans votre cas, cependant, vous semblez travailler principalement avec des applications Web. Les applications Web, de par leur nature, sont multithreads en raison de la façon dont votre serveur Web traite les demandes de chaque utilisateur (c'est-à-dire en parallèle). Bien qu'il soit probablement important pour vous de comprendre la concurrence et la sécurité des threads (en particulier lorsque vous traitez avec des caches et d'autres données partagées), je doute que vous rencontriez trop de cas où il est avantageux de multi-threader le code de l'application Web en interne (c'est-à-dire plusieurs travailleurs fils par demande). En ce sens, je pense qu'être un expert du multi-threading n'est pas vraiment nécessaire pour un développeur web. Elle est souvent posée dans les entretiens, car c'est un sujet assez délicat, et aussi parce que de nombreux enquêteurs recherchent simplement quelques questions 10 minutes avant d'arriver sur le site.

28
Daniel B

Le multi-threading est un hareng rouge. Le multi-threading est un détail d'implémentation du vrai problème qui est Concurrence. Tous les programmes filetés ne sont pas simultanés à cause des verrous et autres.

Les threads ne sont qu'un modèle et un modèle d'implémentation pour implémenter les programmes concurrent.

Par exemple, vous pouvez écrire des logiciels hautement évolutifs et tolérants aux pannes sans avoir à effectuer de multithreading dans des langues comme Erlang.

19
user7519

Je reçois quelques questions sur le multithreading lors des interviews ...

Eh bien, pour réussir les entretiens, le multithreading peut être très important. Citant moi-même , "lors des entretiens avec les candidats de notre équipe, je pose des questions simultanées non pas parce que ces compétences sont importantes dans notre projet ( ce ne sont pas pas ) mais parce que cela me permet d'évaluer la connaissance générale du langage que nous utilisons en quelque sorte ... "

10
gnat

Comprendre comment tirer parti du threading pour améliorer les performances est une compétence essentielle dans l'environnement logiciel d'aujourd'hui, pour la plupart des industries et des applications.

Au minimum, la compréhension des problèmes liés à la concurrence devrait être une donnée.

La remarque évidente que toutes les applications ou tous les environnements ne pourront pas en profiter s'applique, par exemple dans de nombreux systèmes embarqués. Cependant, il semble que le processeur Atom (et al) semble fonctionner pour changer cela (le multicœur léger commence à devenir plus courant).

6
Stephen

On dirait que vous écrivez déjà du code multithread.

La plupart des applications Web Java Java peuvent gérer plusieurs demandes en même temps, et elles le font en utilisant plusieurs threads.

Par conséquent, je dirais qu'il est important de connaître au moins les bases.

4
Tom Jefferys

Réponse courte: très.

Réponse plus longue: les ordinateurs électroniques (basés sur des transistors) approchent rapidement des limites physiques de la technologie. Il devient de plus en plus difficile d'extraire plus d'horloges de chaque cœur tout en gérant la génération de chaleur et les effets quantiques des circuits microscopiques (les chemins de circuit sont déjà placés si près les uns des autres sur les puces modernes qu'un effet appelé "tunnel quantique" peut faire un électron "sauter les pistes" d'un circuit à l'autre, sans avoir besoin des conditions adéquates pour un arc électrique traditionnel); ainsi, pratiquement tous les fabricants de puces se concentrent sur la possibilité de faire en sorte que chaque horloge puisse en faire plus, en mettant plus d '"unités d'exécution" dans chaque CPU. Ensuite, au lieu que l'ordinateur ne fasse qu'une seule chose par horloge, il peut en faire 2, 4 ou même 8. Intel a "HyperThreading", qui divise fondamentalement un cœur de processeur en deux processeurs logiques (avec certaines limitations). Pratiquement tous les fabricants mettent au moins deux cœurs de processeur distincts dans une puce de processeur, et la norme d'or actuelle pour les processeurs de bureau est de quatre cœurs par puce. Huit est possible lorsque deux puces CPU sont utilisées, il existe des cartes mères de serveur conçues pour les processeurs "quad-core" (16 EU plus HT en option), et la prochaine génération de CPU aura probablement six ou huit par puce.

Le résultat de tout cela est que, pour tirer pleinement parti de la façon dont les ordinateurs gagnent en puissance de calcul, vous devez être en mesure de permettre à l'ordinateur de "diviser et conquérir" votre programme. Les langages gérés ont au moins un thread GC qui gère la gestion de la mémoire séparément de votre programme. Certains ont également des threads de "transition" qui gèrent l'interopérabilité COM/OLE (autant pour protéger le "bac à sable" géré que pour les performances). Au-delà de cela, cependant, vous devez vraiment commencer à réfléchir à la façon dont votre programme peut faire plusieurs choses simultanément, et architecturer votre programme avec des fonctionnalités conçues pour permettre à des parties du programme d'être traitées de manière asynchrone. Windows et les utilisateurs de Windows s'attendent pratiquement à ce que votre programme effectue des tâches longues et compliquées dans les threads d'arrière-plan, ce qui maintient l'interface utilisateur de votre programme (qui s'exécute dans le thread principal du programme) "sensible" à la boucle de messages Windows. De toute évidence, les problèmes qui ont des solutions parallélisables (comme le tri) sont des candidats naturels, mais il existe un nombre fini de types de problèmes qui bénéficient de la parallélisation.

2
KeithS

C'est toujours important dans les situations où vous en avez besoin, mais comme beaucoup de choses en développement, c'est le bon outil pour le bon travail. Je suis resté 3 ans sans toucher au filetage, maintenant pratiquement tout ce que je fais a des motifs. Avec les processeurs multicœurs, il y a toujours un grand besoin de thread, mais toutes les raisons traditionnelles sont toujours valables, vous voulez toujours des interfaces réactives et vous voulez toujours pouvoir gérer la synchronisation et passer à autre chose à la fois.

2
Nicholas Smith

Cela m'a laissé me demander à quel point le multithreading est important dans le scénario actuel de l'industrie?

Dans les domaines critiques pour les performances où les performances ne proviennent pas d'un code tiers faisant le gros du travail, mais du nôtre, j'aurais tendance à considérer les choses dans cet ordre d'importance du point de vue du processeur (le GPU est un caractère générique que j'ai gagné pas entrer):

  1. Efficacité de la mémoire (ex: localité de référence).
  2. Algorithmique
  3. Multithreading
  4. SIMD
  5. Autres optimisations (conseils de prédiction de branche statique, par exemple)

Notez que cette liste n'est pas uniquement basée sur l'importance mais sur de nombreuses autres dynamiques comme l'impact qu'elles ont sur la maintenance, leur simplicité (sinon, vaut la peine d'envisager plus à l'avance), leurs interactions avec les autres sur la liste, etc.

efficacité de la mémoire

La plupart pourraient être surpris de mon choix d'efficacité de la mémoire par rapport à l'algorithmique. C'est parce que l'efficacité de la mémoire interagit avec les 4 autres éléments de cette liste, et c'est parce que la prise en compte se fait souvent dans la catégorie "conception" plutôt que dans la catégorie "implémentation". Il y a certes un peu de poulet ou le problème des œufs ici, car la compréhension de l'efficacité de la mémoire nécessite souvent de considérer les 4 éléments de la liste, tandis que les 4 autres éléments nécessitent également de prendre en compte l'efficacité de la mémoire. Pourtant, c'est au cœur de tout.

Par exemple, si nous avons besoin d'une structure de données qui offre un accès séquentiel en temps linéaire et des insertions en temps constant à l'arrière et rien d'autre pour les petits éléments, le choix naïf ici à atteindre serait une liste chaînée. C'est ignorer l'efficacité de la mémoire. Lorsque nous considérons l'efficacité de la mémoire dans le mélange, nous finissons par choisir des structures plus contiguës dans ce scénario, comme des structures basées sur des tableaux évolutifs ou des nœuds plus contigus (ex: un stockant 128 éléments dans un nœud) liés entre eux, ou à tout le moins une liste liée soutenue par un allocateur de pool. Ceux-ci ont un avantage dramatique malgré la même complexité algorithmique. De même, nous choisissons souvent le tri rapide d'un tableau plutôt que le tri par fusion malgré une complexité algorithmique inférieure simplement en raison de l'efficacité de la mémoire.

De même, nous ne pouvons pas avoir un multithreading efficace si nos modèles d'accès à la mémoire sont si granulaires et dispersés dans la nature que nous finissons par maximiser la quantité de faux partage tout en verrouillant aux niveaux les plus granulaires du code. Ainsi, l'efficacité de la mémoire multiplie l'efficacité du multithreading. C'est une condition préalable pour tirer le meilleur parti des threads.

Chaque élément au-dessus de la liste a une interaction complexe avec les données, et se concentrer sur la façon dont les données sont représentées est en fin de compte dans l'efficacité de la mémoire. Chacun de ces éléments peut être goulot d'étranglement avec une manière inappropriée de représenter ou d'accéder aux données.

Une autre raison pour laquelle l'efficacité de la mémoire est si importante est qu'elle peut s'appliquer à travers une base de code entière . Généralement, lorsque les gens s'imaginent que des inefficacités s'accumulent de petites sections de travail ici et là, c'est un signe qu'ils doivent saisir un profileur. Pourtant, les champs à faible latence ou ceux qui traitent d'un matériel très limité trouveront en fait, même après le profilage, des sessions qui n'indiquent aucun hotspot clair (juste des moments dispersés partout) dans une base de code qui est manifestement inefficace avec la façon dont il alloue, copie et accéder à la mémoire. En règle générale, c'est à peu près la seule fois où une base de code entière peut être sensible à un problème de performances qui pourrait conduire à un nouvel ensemble de normes appliquées dans toute la base de code, et l'efficacité de la mémoire est souvent au cœur de celle-ci.

Algorithmique

Celui-ci est à peu près une donnée, car le choix dans un algorithme de tri peut faire la différence entre une entrée massive prenant des mois à trier et des secondes à trier. Cela fait le plus grand impact de tous si le choix est entre, disons, des algorithmes quadratiques ou cubiques vraiment inférieurs à la normale et un algorithme linéaire, ou entre linéaire et logarithmique ou constant, au moins jusqu'à ce que nous ayons comme 1 000 000 de machines de base (auquel cas la mémoire l’efficacité deviendrait encore plus importante).

Cependant, ce n'est pas en haut de ma liste personnelle, car toute personne compétente dans son domaine devrait utiliser une structure d'accélération pour l'abattage tronconique, par exemple. Nous sommes saturés de connaissances algorithmiques, et savoir des choses comme l'utilisation d'une variante d'un trie comme un arbre radix pour les recherches basées sur des préfixes est un truc de bébé. En l'absence de ce type de connaissances de base dans le domaine dans lequel nous travaillons, l'efficacité algorithmique atteindrait certainement le sommet, mais souvent l'efficacité algorithmique est triviale.

Inventer de nouveaux algorithmes peut également être une nécessité dans certains domaines (ex: dans le traitement des maillages, j'ai dû en inventer des centaines car ils n'existaient pas auparavant, ou les implémentations de fonctionnalités similaires dans d'autres produits étaient des secrets propriétaires, non publiés dans un article) ). Cependant, une fois que nous avons dépassé la partie de résolution de problèmes et trouvé un moyen d'obtenir les bons résultats, et une fois que l'efficacité devient l'objectif, la seule façon de vraiment gagner est de considérer comment nous interagissons avec les données (mémoire). Sans comprendre l'efficacité de la mémoire, le nouvel algorithme peut devenir inutilement complexe avec des efforts futiles pour le rendre plus rapide, alors que la seule chose dont il avait besoin était un peu plus de considération de l'efficacité de la mémoire pour produire un algorithme plus simple et plus élégant.

Enfin, les algorithmes ont tendance à être plus dans la catégorie "implémentation" que l'efficacité de la mémoire. Ils sont souvent plus faciles à améliorer avec le recul, même avec un algorithme sous-optimal utilisé initialement. Par exemple, un algorithme de traitement d'image inférieur est souvent simplement implémenté à un endroit local dans la base de code. Il peut être échangé avec un meilleur plus tard. Cependant, si tous les algorithmes de traitement d'image sont liés à une interface Pixel qui a une représentation mémoire sous-optimale, mais la seule façon de la corriger est de changer la façon dont plusieurs pixels sont représentés (et pas un seul) , alors nous sommes souvent SOL et nous devrons réécrire complètement la base de code vers une interface Image. Le même genre de chose vaut pour remplacer un algorithme de tri - c'est généralement une implémentation détail, alors qu'une modification complète de la représentation sous-jacente des données en cours de tri ou de la façon dont elles sont transmises via les messages peut nécessiter une refonte des interfaces.

Multithreading

Le multithreading est difficile dans le contexte des performances, car il s'agit d'une optimisation à micro-niveau jouant sur les caractéristiques matérielles, mais notre matériel évolue vraiment dans cette direction. J'ai déjà des pairs qui ont 32 cœurs (je n'en ai que 4).

Pourtant, la lecture multiple est parmi les micro-optimisations les plus dangereuses probablement connues d'un professionnel si le but est utilisé pour accélérer le logiciel. La condition de concurrence est à peu près le bug le plus mortel possible, car il est de nature indéterministe (peut-être n'apparaissant qu'une fois tous les quelques mois sur la machine d'un développeur à un moment très gênant en dehors du contexte de débogage, le cas échéant). Il a donc sans doute la dégradation la plus négative de la maintenabilité et de l'exactitude potentielle du code parmi tous ces éléments, d'autant plus que les bogues liés au multithreading peuvent facilement passer sous le radar des tests même les plus minutieux.

Néanmoins, cela devient si important. Bien qu'il ne puisse pas toujours l'emporter sur quelque chose comme l'efficacité de la mémoire (qui peut parfois rendre les choses cent fois plus rapides) compte tenu du nombre de cœurs que nous avons actuellement, nous voyons de plus en plus de cœurs. Bien sûr, même avec des machines à 100 cœurs, je mettrais toujours l'efficacité de la mémoire en tête de liste, car l'efficacité des threads est généralement impossible sans elle. Un programme peut utiliser une centaine de threads sur une telle machine et être lent, sans représentation efficace de la mémoire ni modèles d'accès (qui seront liés aux modèles de verrouillage).

SIMD

SIMD est également un peu gênant, car les registres deviennent en fait plus larges, avec des plans pour aller encore plus loin. À l'origine, nous avons vu des registres MMX 64 bits suivis de registres XMM 128 bits capables de 4 opérations SPFP en parallèle. Nous voyons maintenant des registres YMM 256 bits capables de 8 en parallèle. Et il existe déjà des plans en place pour les registres 512 bits qui permettraient 16 en parallèle.

Celles-ci interagiraient et se multiplieraient avec l'efficacité du multithreading. Pourtant, SIMD peut dégrader la maintenabilité tout autant que le multithreading. Même si les bogues qui leur sont liés ne sont pas nécessairement aussi difficiles à reproduire et à corriger qu'un blocage ou une condition de concurrence critique, la portabilité est maladroite et garantir que le code peut s'exécuter sur la machine de chacun (et en utilisant les instructions appropriées en fonction de leurs capacités matérielles) est gênant.

Une autre chose est que, bien que les compilateurs d'aujourd'hui ne battent généralement pas le code SIMD écrit par des experts, ils réussissent facilement les tentatives naïves. Ils pourraient s'améliorer au point où nous n'aurions plus à le faire manuellement, ou du moins sans être trop manuel pour écrire des codes intrinsèques ou des codes d'assemblage directs (peut-être juste un petit guide humain).

Encore une fois cependant, sans une disposition de mémoire efficace pour le traitement vectorisé, SIMD est inutile. Nous finirons par charger un champ scalaire dans un registre large uniquement pour effectuer une opération dessus. Au cœur de tous ces éléments se trouve une dépendance vis-à-vis des dispositions de mémoire pour être vraiment efficace.

Autres optimisations

Ce sont souvent ce que je suggère que nous commencions à appeler "micro" de nos jours si la Parole suggère non seulement d'aller au-delà de la focalisation algorithmique, mais vers des changements qui ont un impact minuscule sur les performances.

Souvent, essayer d'optimiser la prédiction de branche nécessite un changement d'algorithme ou d'efficacité de la mémoire, par ex. Si cela est tenté simplement par des astuces et en réorganisant le code pour la prédiction statique, cela n'a tendance qu'à améliorer la première exécution de ce code, ce qui rend les effets discutables sinon souvent carrément négligeables.

Retour au multithreading pour les performances

Alors, quelle est l'importance du multithreading dans un contexte de performance? Sur ma machine à 4 cœurs, il peut idéalement rendre les choses environ 5 fois plus rapides (ce que je peux obtenir avec l'hyperthreading). Ce serait beaucoup plus important pour mon collègue qui a 32 cœurs. Et cela deviendra de plus en plus important dans les années à venir.

C'est donc assez important. Mais il est inutile de simplement jeter un tas de fils sur le problème si l'efficacité de la mémoire n'est pas là pour permettre l'utilisation des verrous avec parcimonie, pour réduire les faux partages, etc.

Multithreading en dehors des performances

Le multithreading n'est pas toujours une question de performances absolues dans un sens de débit simple. Parfois, il est utilisé pour équilibrer une charge, même au coût possible du débit, pour améliorer la réactivité à l'utilisateur, ou pour permettre à l'utilisateur de faire plus de multitâche sans attendre la fin des choses (ex: continuer à naviguer tout en téléchargeant un fichier).

Dans ces cas, je suggère que le multithreading augmente encore plus haut (peut-être même au-dessus de l'efficacité de la mémoire), car il s'agit alors de conception utilisateur plutôt que de tirer le meilleur parti du matériel. Cela va souvent dominer les conceptions d'interface et la façon dont nous structurons l'ensemble de notre base de code dans de tels scénarios.

Lorsque nous ne sommes pas simplement en train de paralléliser une boucle étroite accédant à une structure de données massive, le multithreading va dans la catégorie "design" vraiment hardcore, et la conception l'emporte toujours sur la mise en œuvre.

Donc, dans ces cas, je dirais que considérer le multithread en amont est absolument critique, encore plus que la représentation et l'accès à la mémoire.

1
user204677

Juste un avertissement sur le multithreading: plus de threads ne signifient pas une meilleure efficacité. S'ils ne sont pas gérés correctement, ils peuvent ralentir le système. L'acteur de Scala améliore le filetage de Java et maximise l'utilisation du système (mentionnez-le car vous êtes un développeur Java).

EDIT: Voici quelques éléments à garder à l'esprit concernant les inconvénients du multithreading:

  • interférence des threads lors du partage des ressources matérielles
  • Les temps d'exécution d'un seul thread ne sont pas améliorés mais peuvent être dégradés, même lorsqu'un seul thread est en cours d'exécution. Cela est dû à des fréquences plus lentes et/ou à des étages de pipeline supplémentaires qui sont nécessaires pour s'adapter au matériel de commutation de threads.
  • La prise en charge matérielle du multithreading est plus visible pour les logiciels, nécessitant ainsi plus de modifications à la fois des programmes d'application et des systèmes d'exploitation que le multitraitement.
  • Difficulté à gérer la simultanéité.
  • Difficulté de test.

En outre, ce lien pourrait être utile à peu près de la même manière.

1
c0da

La programmation simultanée et parallèle devient ce qui devient important. Les threads ne sont qu'un modèle de programmation permettant de faire plusieurs choses en même temps (et non en pseudo-parallèle comme c'était le cas avant la montée en puissance des processeurs multicœurs). Le multi-threading est (à mon humble avis) critiqué pour être complexe et dangereux car les threads partagent de nombreuses ressources et le programmeur est responsable de les faire coopérer. Sinon, vous vous retrouvez avec des blocages qui sont difficiles à déboguer.

0
sakisk

Historiquement, les gens devaient lutter en faisant à la main une programmation multithread. Ils devaient travailler avec tous les composants de base (threads, sémaphores, mutex, verrous, etc.) directement.

Tous ces efforts ont abouti à des applications qui ont pu évoluer en ajoutant des processeurs supplémentaires à un seul système. Cette évolutivité verticale est limitée par "quel est le plus grand serveur que je puisse acheter".

De nos jours, je constate une évolution vers l'utilisation de plus de cadres et de différents modèles de conception pour la conception de logiciels. MapReduce est un de ces modèles axé sur le traitement par lots.

L'objectif est une mise à l'échelle horizontale. Ajouter plus de serveurs standard au lieu d'acheter de plus gros serveurs.

Cela dit, le fait demeure qu'il est très important de vraiment comprendre la programmation multithread. J'ai été dans une situation où quelqu'un a créé une condition de course et ne savait même pas ce qu'est une condition de course jusqu'à ce que nous remarquions d'étranges erreurs lors des tests.

0
Niels Basjes

Étant donné que nous pouvons avoir besoin de contacter de nombreuses applications externes, il peut y avoir un processus d'arrière-plan qui devrait se produire lorsque l'interaction avec le système externe prend plus de temps et l'utilisateur final ne peut pas attendre que le processus soit terminé. donc le multithreading est important ..

nous utilisons dans notre application, nous essayons d'abord de contacter le système externe s'il est en panne, puis nous enregistrons la demande dans la base de données et couvrons un thread pour terminer le processus en arrière-plan. Peut également être nécessaire dans les opérations par lots.

0
TPReddy