web-dev-qa-db-fra.com

Processus Erlang vs Java Threads

Je lis le livre "Elixir in Action" de Saša Jurić, et dans le premier chapitre il dit:

Les processus d'Erlang sont complètement isolés les uns des autres. Ils ne partagent pas de mémoire et le plantage d'un processus n'entraîne pas le plantage des autres processus.

N'est-ce pas vrai pour les threads Java aussi? Je veux dire quand Java thread se bloque, il ne plante pas non plus les autres threads - en particulier, si nous cherchons au niveau des threads de traitement des requêtes (permet d'exclure le thread main de cette discussion)

57
Wand Maker

Répétez après moi: "Ce sont des paradigmes différents"

Dites cela à haute voix environ 20 fois - c'est notre mantra pour le moment.

Si nous vraiment devons comparer les pommes et les oranges, considérons au moins où se croisent les aspects communs du "fruit".

Les "objets" Java sont un Java. C'est-à-dire un objet (essentiellement une structure avec bras et jambes encapsulés n peu plus strictement appliqué qu'en C++ ) est le principal outil avec lequel vous modélisez le monde. Vous pensez "Cet objet connaît/a Data {X,Y,Z} et exécute Functions {A(),B(),C()} dessus, porte le Data partout où il va, et peut communiquer avec d'autres objets en appelant des fonctions/méthodes définies dans le cadre de leur interface publique. C'est un nom, et ce nom does des trucs. ". C'est-à-dire que vous orientez votre processus de pensée autour de ces unités de calcul. Le cas par défaut est que les choses qui se produisent parmi les objets se produisent dans l'ordre, et un plantage interrompt cette séquence. Ils sont appelés "objets" et donc (si nous ne tenons pas compte de la signification originale d'Alan Kay) nous obtenons une "orientation d'objet".

Les "processus" Erlang sont l'unité de calcul de base d'un programmeur Erlang. Un processus (essentiellement un programme séquentiel autonome fonctionnant dans son temps et son espace) est l'outil principal avec lequel un Erlanger modélise le monde (1) . Similaire à la façon dont Java définissent un niveau d'encapsulation, les processus Erlang définissent également le niveau d'encapsulation, mais dans le cas d'Erlang les unités de calcul sont complètement coupés les uns des autres. Vous ne pouvez pas appeler une méthode ou une fonction sur un autre processus, vous ne pouvez pas accéder aux données qui y vivent, ni un processus ne s'exécute même dans le même contexte de synchronisation que les autres processus, et il n'y a aucune garantie quant à l'ordre de réception des messages relatifs à d'autres processus qui peuvent envoyer des messages. Ils peuvent tout aussi bien être sur différentes planètes (et, à bien y penser, c'est en fait plausible). Ils peuvent se bloquer indépendamment les uns des autres et les autres processus ne sont impactés que s'ils ont délibérément choisi d'être impactés (et même cela implique un message: s'inscrire essentiellement pour recevoir une note de suicide du processus mort qui lui-même n'est pas garanti d'arriver en aucune sorte d'ordre par rapport au système dans son ensemble, auquel vous pouvez ou non choisir de réagir).

Java traite la complexité directement dans les algorithmes composés: comment les objets fonctionnent ensemble pour résoudre un problème. Il est conçu pour le faire dans un seul contexte d'exécution, et le cas par défaut dans Java est une exécution séquentielle. Plusieurs threads dans Java indique plusieurs contextes en cours d'exécution et est un sujet très complexe en raison de l'impact des activités dans différents contextes temporels (et sur le système dans son ensemble: d'où la programmation défensive, les schémas d'exception, etc.). Dire "multi-thread" dans Java signifie quelque chose de différent de ce qu'il fait à Erlang, en fait cela n'est même jamais dit à Erlang car c'est toujours le cas de base. Notez ici que Java = les threads impliquent une ségrégation en fonction du temps, pas de la mémoire ou des références visibles - la visibilité dans Java est contrôlée manuellement en choisissant ce qui est privé et ce qui est public; les éléments universellement accessibles d'un système doivent être soit conçu pour être "threadsafe" et réentrant, séquencé via des mécanismes de mise en file d'attente, ou utiliser des mécanismes de verrouillage. En bref: la planification est un problème géré lu/simultané Java programmes.

Erlang sépare le contexte d'exécution de chaque processus en termes de synchronisation d'exécution (planification), d'accès à la mémoire et de visibilité de référence et, ce faisant, simplifie chaque composant d'un algorithme en l'isolant complètement. Ce n'est pas seulement le cas par défaut, c'est le seul cas disponible sous ce modèle de calcul. Cela se fait au prix de ne jamais savoir exactement la séquence d'une opération donnée une fois qu'une partie de vos séquences de traitement franchit une barrière de message - car les messages sont tous essentiellement des protocoles réseau et aucun appel de méthode ne peut être garanti pour s'exécuter dans une certaine le contexte. Cela serait analogue à la création d'une instance JVM par objet, et leur permettant uniquement de communiquer entre les sockets - ce serait ridiculement lourd en Java, mais c'est la façon dont Erlang est conçu pour fonctionner (d'ailleurs, c'est également la base du concept d'écrire des "microservices Java" si l'on abandonne le bagage orienté web que le mot à la mode a tendance à impliquer - les programmes Erlang sont, par défaut, des essaims de microservices). Tout est question de compromis.

Ce sont des paradigmes différents. Le point commun le plus proche que nous pouvons trouver est de dire que du point de vue du programmeur, les processus Erlang sont analogues aux objets Java. Si nous devons trouver quelque chose à comparer Java threads pour ... eh bien, nous n'allons tout simplement pas trouver quelque chose comme ça à Erlang, car il n'y a pas un tel concept comparable à Erlang. Pour battre un cheval mort: ce sont des paradigmes différents. Si vous écrivez quelques programmes non triviaux dans Erlang, cela deviendra évident.

Notez que je dis "ce sont des paradigmes différents" mais je n'ai même pas abordé le sujet de OOP vs FP. La différence entre "penser en Java" et "penser en Erlang" est plus fondamentale que OOP vs FP.

S'il est vrai que la fondation "orientée simultanée" ou "orientée processus" d'Erlang est plus proche de ce qu'Alan Kay avait en tête lorsqu'il a inventé le terme "orienté objet" (2), ce n'est pas vraiment le point ici. Ce que Kay voulait dire, c'est que l'on peut réduire la complexité cognitive d'un système en coupant vos ordinateurs en morceaux discrets, et l'isolement est nécessaire pour cela. Java accomplit cela d'une manière qui le laisse encore fondamentalement de nature procédurale, mais structure le code autour d'une syntaxe spéciale sur les fermetures de répartition d'ordre supérieur appelées "définitions de classe". Erlang le fait en divisant le fonctionnement contexte par objet. Cela signifie que les machins Erlang ne peuvent pas appeler de méthodes les uns sur les autres, mais Java le peuvent. Cela signifie que les machins Erlang peuvent planter isolément mais Java = les bidules ne peuvent pas. Un grand nombre d'implications découlent de cette différence fondamentale - d'où les "paradigmes différents".


Notes de bas de page:

  1. Incidemment, Erlang implémente une version de " le modèle d'acteur ", mais nous n'utilisons pas cette terminologie car Erlang est antérieur à la popularisation de ce modèle. Joe ne le savait pas quand il a conçu Erlang et écrit sa thèse .
  2. Alan Kay a beaucoup parlé de ce qu'il voulait dire quand il a inventé le terme "orienté objet", le plus intéressant étant son point de vue sur la messagerie (notification à sens unique d'un processus indépendant avec son propre timing et sa mémoire à un autre) Appels VS (appels de fonction ou de méthode dans un contexte d'exécution séquentielle avec mémoire partagée) - et comment les lignes se brouillent un peu entre l'interface de programmation telle que présentée par le langage de programmation et l'implémentation ci-dessous.
91
zxq9

Certainement pas. Tous les threads de Java partagent le même espace d'adressage, il est donc possible qu'un thread supprime les éléments appartenant à un autre thread. Dans Erlang VM ce n'est pas le cas) t possible car chaque processus est isolé de tous les autres. C'est tout leur intérêt. Chaque fois que vous voulez qu'un processus fasse quelque chose avec les données d'un autre, votre code doit envoyer un message à l'autre processus. Les seules choses partagées entre les processus sont de grands objets binaires et ceux-ci sont immuables.

16
Daniel

Les processus Java peuvent en effet partager la mémoire. Par exemple, vous pouvez transmettre la même instance à deux threads distincts et les deux peuvent manipuler son état, entraînant des problèmes potentiels tels que blocages .

Elixir/Erlang, d'autre part, résout ce problème par le concept d'immuabilité, donc lorsque vous passez quelque chose à un processus, ce sera une copie de la valeur d'origine.

15
Patrick Oscity

lorsque Java meurt, il n'a pas non plus d'impact sur les autres threads

Permettez-moi de poser une contre-question: pourquoi pensez-vous que Thread.stop() est obsolète depuis plus d'une décennie? La raison en est précisément la négation de votre déclaration ci-dessus.

Pour donner deux exemples spécifiques: vous stop() un thread pendant qu'il exécute quelque chose d'aussi inoffensif que System.out.println() ou Math.random(). Résultat: ces deux fonctionnalités sont désormais rompues pour l'ensemble de la JVM. Il en va de même pour tout autre code synchronisé que votre application peut exécuter.

si nous examinons les fils de traitement des demandes

L'application peut être théoriquement codée de manière à ce qu'aucune ressource partagée protégée par des verrous ne soit utilisée; cependant, cela ne fera que montrer dans quelle mesure Java sont dépendants du code. Et l '"indépendance" obtenue ne concernera que les threads de traitement des demandes, pas tous threads dans une telle application.

5
Marko Topolnik

Pour compléter les réponses précédentes, Java ont deux types: démon et non démon.

Pour changer le type d'un thread, vous pouvez appeler .setDaemon(boolean on) . La différence est qu'un thread démon n'empêche pas la JVM de se fermer. Comme le dit Javadoc pour Thread:

La machine virtuelle Java se ferme lorsque les seuls threads en cours d'exécution sont tous des threads démon.

Cela signifie que les threads utilisateur (ceux qui ne sont pas spécifiquement définis pour être des démons) empêchent la JVM de se terminer. D'un autre côté, les threads démon PEUVENT ÊTRE EN COURS D'EXÉCUTION lorsque tous les threads non démon sont terminés, auquel cas la JVM se fermera. Donc, pour répondre à votre question: vous pouvez démarrer un thread qui ne quitte pas la JVM à la fin.

Quant à la comparaison avec Erlang/Elixir, n'oubliez pas: ce sont des paradigmes différents, comme déjà mentionné.

Il n'est pas impossible pour la JVM d'imiter le comportement d'Erlang bien que ce ne soit pas pour ce à quoi elle était destinée et, par conséquent, cela va avec beaucoup de compromis. Les projets suivants essaient d'accomplir cela:

2
Olinasc

N'est-ce pas vrai pour les fils Java aussi? Je veux dire quand Java tombe en panne, il ne plante pas non plus les autres fils)

Oui et non. J'explique:

  • Se référant à la mémoire partagée: différents threads dans un processus Java partagent le tas entier, donc les threads peuvent interagir d'un grand nombre de façons planifiées et non planifiées. Cependant objets dans le pile (par exemple un contexte que vous transmettez à la méthode appelée) ou un ThreadLocal sont leurs propres threads (à moins qu'ils ne commencent à partager des références).

  • Crashing: Si un thread se bloque dans Java (un Throwable est propagé dans Thread.run(), ou quelque chose est bouclé ou bloqué), cet incident pourrait ne pas affecter les autres (par exemple, un pool de connexions dans un serveur continuera de fonctionner). Cependant lorsque différents threads interagissent. D'autres threads seront facilement bloqués si l'un d'eux se termine anormalement (par exemple, un thread essayant de lire à partir d'un vide d'un autre fil qui n'a pas fermé son extrémité. Donc, à moins que les développeurs ne soient très paranoïaque attention, il est très probable que des effets secondaires se produisent.

Je doute que tout autre paradigme ait l'intention que les threads fonctionnent comme des îles totalement indépendantes. Ils doivent partager des informations et coordonner en quelque sorte. Et puis il y aura la chance de gâcher les choses. C'est juste qu'ils adopteront une approche plus défensive qui "vous donne moins de corde pour vous pendre" (même idiome que pour les pointeurs).

1
Javier