Bien que les threads semblent être un petit pas par rapport au calcul séquentiel, ils représentent en fait un pas énorme. Ils rejettent les propriétés les plus essentielles et les plus attrayantes du calcul séquentiel: la compréhensibilité, la prévisibilité et le déterminisme. Les threads, en tant que modèle de calcul, sont extrêmement non déterministes et le travail du programmeur devient un travail d'élagage que le non déterminisme.
- Le problème des threads (www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf).
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.