web-dev-qa-db-fra.com

Comment expliquer pourquoi le multi-threading est difficile

Je suis un assez bon programmeur, mon patron est aussi un assez bon programmeur. Bien qu'il semble sous-estimer certaines tâches telles que le multi-threading et la difficulté que cela peut être (je trouve cela très difficile pour autre chose que d'exécuter quelques threads, d'attendre que tout se termine, puis de retourner les résultats).

Au moment où vous commencez à vous soucier des blocages et des conditions de course, je trouve cela très difficile, mais le patron ne semble pas l'apprécier - je ne pense pas qu'il l'ait jamais rencontré. Il suffit d'appuyer sur un verrou, c'est à peu près l'attitude.

Alors, comment puis-je le présenter ou expliquer pourquoi il sous-estime peut-être les complexités de la concurrence, du parallélisme et du multithreading? Ou peut-être que je me trompe?

Edit: Juste un peu sur ce qu'il a fait - parcourez une liste, pour chaque élément de cette liste, créez un thread qui exécute une commande de mise à jour de la base de données en fonction des informations contenues dans cet élément. Je ne sais pas comment il contrôlait le nombre de threads exécutés en même temps, je suppose qu'il a dû les ajouter à une file d'attente s'il y en avait trop en cours d'exécution (il n'aurait pas utilisé de sémaphore).

86
Mr Shoubs
  1. Si vous pouvez compter sur n'importe quelle expérience mathématique, illustrez comment un flux d'exécution normal qui est essentiellement déterministe devient non seulement non déterministe avec plusieurs threads, mais exponentiellement complexe, car vous devez vous assurer que chaque entrelacement possible des instructions machine fera toujours la bonne chose. Un exemple simple d'une mise à jour perdue ou d'une situation de lecture incorrecte est souvent révélateur.

  2. "Slap a lock on it" is the trivial solution ... it résout tous vos problèmes if vous n'êtes pas préoccupé par les performances. Essayez d'illustrer la quantité de performances que cela représenterait si, par exemple, Amazon devait verrouiller toute la côte est chaque fois qu'une personne à Atlanta commande un livre!

29
Kilian Foth

Multi-threading is simple. Le codage d'une application pour le multi-threading est très, très facile.

Il y a une astuce simple, et c'est d'utiliser une file d'attente de messages bien conçue (faites pas lancez la vôtre) pour passer des données entre les threads.

La partie difficile essaie d'avoir plusieurs threads comme par magie pour mettre à jour un objet partagé d'une manière ou d'une autre. C'est à ce moment que cela devient sujet aux erreurs car les gens ne font pas attention aux conditions de course qui sont présentes.

Beaucoup de gens n'utilisent pas les files d'attente de messages et essaient de mettre à jour les objets partagés et de se créer des problèmes.

Ce qui devient difficile, c'est la conception d'un algorithme qui fonctionne bien lors du passage de données entre plusieurs files d'attente. C'est difficile. Mais la mécanique des threads coexistants (via des files d'attente partagées) est facile.

Notez également que les threads partagent les ressources d'E/S. Un programme lié aux E/S (c'est-à-dire les connexions réseau, les opérations sur les fichiers ou les opérations de base de données) a peu de chances d'aller plus vite avec beaucoup de threads.

Si vous voulez illustrer le problème de mise à jour des objets partagés, c'est simple. Asseyez-vous sur la table avec un tas de cartes en papier. Notez un ensemble simple de calculs - 4 ou 6 formules simples - avec beaucoup d'espace en bas de la page.

Voici le jeu. Vous lisez chacun une formule, écrivez une réponse et mettez une carte avec la réponse.

Chacun de vous fera la moitié du travail, non? Vous avez terminé en deux fois moins de temps, non?

Si votre patron ne pense pas beaucoup et commence juste, vous finirez par être en conflit d'une manière ou d'une autre et en écrivant des réponses à la même formule. Cela n'a pas fonctionné car il y a une condition de concurrence inhérente entre vous deux avant de lire. Rien ne vous empêche à la fois de lire la même formule et de vous écraser les réponses.

Il existe de nombreuses façons de créer des conditions de concurrence avec des ressources mal ou non verrouillées.

Si vous voulez éviter tous les conflits, vous découpez le papier en une pile de formules. Vous en retirez un de la file d'attente, notez la réponse et publiez les réponses. Aucun conflit car vous lisez tous les deux à partir d'une file d'attente de messages à lecteur unique.

79
S.Lott

La programmation multithread est probablement la solution la plus difficile à la concurrence. Il s'agit essentiellement d'une abstraction de bas niveau de ce que fait réellement la machine.

Il existe un certain nombre d'approches, telles que modèle d'acteur ou (logiciel) mémoire transactionnelle , qui sont beaucoup plus faciles. Ou travailler avec des structures de données immuables (telles que des listes et des arbres).

En règle générale, une séparation appropriée des préoccupations facilite le multithread. Quelque chose, qui est trop souvent oublié, lorsque les gens génèrent 20 threads, tous essayant de traiter le même tampon. Utilisez des réacteurs où vous avez besoin de synchronisation et passez généralement des données entre différents travailleurs avec des files d'attente de messages.
Si vous avez un verrou dans votre logique d'application, vous avez fait quelque chose de mal.

Alors oui, techniquement, le multi-threading est difficile.
. Cela revient à ramener un problème à un modèle d'exécution non simultané. Plus vous le faites, plus il est probable que vous n'ayez qu'un seul thread en cours d'exécution (ou 0 dans une impasse). Cela va à l'encontre du but.
C'est comme dire "Résoudre les problèmes du 3ème monde est facile. Il suffit de lancer une bombe dessus." Tout simplement parce qu'il existe une solution triviale, cela ne rend pas le problème trivial, car vous vous souciez de la qualité du résultat.

Mais dans la pratique, la résolution de ces problèmes est aussi difficile que tout autre problème de programmation et se fait mieux avec des abstractions appropriées. Ce qui le rend assez facile en fait.

25
back2dos

Je pense qu'il y a un angle non technique à cette question - l'OMI c'est une question de confiance. On nous demande souvent de reproduire des applications complexes comme - oh, je ne sais pas - Facebook par exemple. J'en suis venu à la conclusion que si vous devez expliquer la complexité d'une tâche aux non-initiés/à la direction - alors quelque chose de pourri au Danemark.

Même si d'autres programmeurs ninja pouvaient faire la tâche en 5 minutes, vos estimations sont basées sur vos capacités personnelles. Votre interlocuteur doit soit apprendre à faire confiance à votre opinion sur la question, soit embaucher quelqu'un dont il est prêt à accepter la Parole.

Le défi n'est pas de relayer les implications techniques, que les gens ont tendance à ignorer ou sont incapables de saisir à travers la conversation, mais d'établir une relation de respect mutuel.

14
sunwukung

Une expérience de pensée simple pour comprendre les impasses est le problème " philosophe de la salle à manger ". L'un des exemples que j'ai tendance à utiliser pour décrire à quel point les conditions de course peuvent être mauvaises est la situation Therac 25 .

"Il suffit de taper un verrou dessus" est la mentalité de quelqu'un qui n'a pas rencontré de bugs difficiles avec le multi-threading. Et il est possible que il pense que vous surestimez la gravité de la situation (je ne le fais pas - il est possible de faire sauter des trucs ou de tuer des gens avec les bogues des conditions de compétition, en particulier avec les logiciels intégrés qui se retrouvent dans les voitures).

6
Tangurena

Réponse courte en deux mots: NONDETERMINISME OBSERVABLE

Réponse longue: Cela dépend de l'approche de la programmation simultanée que vous utilisez compte tenu de votre problème. Dans le livre Concepts, Techniques, and Models of Computer Programming , les auteurs expliquent clairement quatre principales approches pratiques pour écrire des programmes simultanés:

  • Programmation séquentielle : une approche de base sans concurrence;
  • Concurrence déclarative : utilisable lorsqu'il n'y a pas de non-déterminisme observable;
  • Concurrence de passage de message : message simultané passant entre de nombreuses entités, où chaque entité traite en interne le message de manière séquentielle;
  • Concurrence d'état partagé : thread mettant à jour les objets passifs partagés au moyen d'actions atomiques à grain grossier, par exemple serrures, moniteurs et transactions;

Maintenant, la plus simple de ces quatre approches en dehors de la programmation séquentielle évidente est la concurrence déclarative , car les programmes écrits en utilisant cette approche ont pas de non-déterminisme observable . En d'autres termes, il n'y a aucune condition de concurrence , car la condition de concurrence n'est qu'un comportement non déterministe observable.

Mais le manque de non-déterminisme observable signifie qu'il y a quelques problèmes que nous ne pouvons pas résoudre en utilisant la concurrence déclarative. C'est là que les deux dernières approches moins faciles entrent en jeu. La partie pas si facile est une conséquence du non-déterminisme observable. Maintenant, ils relèvent tous deux d'un modèle concurrent avec état et sont également équivalents en termes d'expressivité. Mais en raison du nombre toujours croissant de cœurs par CPU, il semble que l'industrie s'est récemment intéressée davantage à la simultanéité du passage des messages, comme en témoigne l'augmentation des bibliothèques de passage de messages (par exemple Akka pour JVM) ou des langages de programmation (par exemple Erlang ).

La bibliothèque Akka mentionnée précédemment, qui s'appuie sur un modèle théorique d'acteur, simplifie la création d'applications simultanées, car vous n'avez plus à vous soucier des verrous, des moniteurs ou des transactions. D'autre part, cela nécessite une approche différente de la conception de la solution, c'est-à-dire une réflexion sur la manière de composer des acteurs hiérarchiquement composites. On pourrait dire que cela nécessite un état d'esprit totalement différent, ce qui peut être encore plus difficile à la fin que l'utilisation de la concurrence partagée à l'état simple.

La programmation simultanée est difficile à cause du non-déterminisme observable, mais lorsque vous utilisez la bonne approche pour le problème donné et la bonne bibliothèque qui prend en charge cette approche, alors beaucoup de les problèmes peuvent être évités.

3
Jernej Jerin

Les applications simultanées ne sont pas déterministes. Avec la quantité exceptionnellement petite de code global que le programmeur a reconnu comme vulnérable, vous ne contrôlez pas quand une partie d'un thread/processus s'exécute par rapport à une partie d'un autre thread. Le test est plus difficile, prend plus de temps et il est peu probable qu'il trouve tous les défauts liés à la concurrence. Les défauts, s'ils sont découverts, sont souvent subtils et ne peuvent pas être reproduits de manière cohérente, ce qui rend la réparation difficile.

Par conséquent, la seule application simultanée correcte est celle qui est prouvée correcte, quelque chose qui n'est pas souvent pratiqué dans le développement de logiciels. Par conséquent, la réponse de S.Lot est le meilleur conseil général, car la transmission de messages est relativement facile à prouver.

3
mattnz

On m'a d'abord appris qu'il pouvait soulever des problèmes en voyant un programme simple qui démarrait 2 threads et les faisait imprimer tous les deux sur la console en même temps de 1 à 100. Au lieu de:

1
1
2
2
3
3
...

Vous obtenez quelque chose de plus comme ceci:

1
2
1
3
2
3
...

Exécutez-le à nouveau et vous obtiendrez des résultats totalement différents.

La plupart d'entre nous ont été formés pour supposer que notre code s'exécutera séquentiellement. Avec la plupart des multi-threads, nous ne pouvons pas tenir cela pour acquis "prêt à l'emploi".

0
Morgan Herlocker