web-dev-qa-db-fra.com

Quels sont les inconvénients de faire une implémentation d'exécution JavaScript multi-thread?

J'ai travaillé sur une implémentation d'exécution JavaScript à plusieurs threads la semaine dernière. J'ai une preuve de concept faite en C++ en utilisant JavaScriptCore et boost.

L'architecture est simple: lorsque le runtime a fini d'évaluer le script principal, il se lance et rejoint un pool de threads, qui commence à choisir des tâches dans une file d'attente prioritaire partagée, si deux tâches tentent d'accéder simultanément à une variable, elle est marquée atomique et se disputent l'accès .

Working multithreaded node.js runtime

Le problème est que lorsque je montre cette conception à un programmeur JavaScript, je reçois des commentaires extrêmement négatifs et je ne sais pas pourquoi. Même en privé, ils disent tous que JavaScript est censé être monothread, que les bibliothèques existantes devraient être réécrites, et que les gremlins apparaîtront et mangeront chaque être vivant si je continue à travailler dessus.

À l'origine, j'avais une implémentation native de coroutine (en utilisant des contextes de boost), mais je devais l'abandonner (JavaScriptCore est pédant à propos de la pile), et je ne voulais pas risquer leur colère alors j'ai décidé de ne pas le mentionner.

Qu'est-ce que tu penses? Le JavaScript est-il censé être monothread et doit-il être laissé seul? Pourquoi tout le monde est contre l'idée d'un runtime JavaScript simultané?

Edit: Le projet est maintenant sur GitHub , expérimentez-le vous-même et faites-moi savoir ce que vous en pensez.

Ce qui suit est une image des promesses s'exécutant sur tous les cœurs de processeur en parallèle sans contention:

Running promises concurrently.

51
voodooattack

1) Le multithreading est extrêmement difficile, et malheureusement, la façon dont vous avez présenté cette idée jusqu'à présent implique que vous sous-estimez gravement la difficulté.

Pour le moment, il semble que vous "ajoutiez simplement des fils" à la langue et que vous vous demandiez comment la rendre correcte et performante plus tard. En particulier:

si deux tâches tentent d'accéder simultanément à une variable, elle devient marquée atomique et se disputent l'accès.
...
Je suis d'accord que les variables atomiques ne résoudront pas tout, mais travailler sur une solution au problème de synchronisation est mon prochain objectif.

Ajouter des threads à Javascript sans "solution pour le problème de synchronisation" serait comme ajouter des entiers à Javascript sans "solution pour le problème d'ajout". Il est si fondamental à la nature du problème qu'il est pratiquement inutile de discuter de la question de savoir si le multithreading vaut la peine d'être ajouté sans solution spécifique à l'esprit, peu importe à quel point nous le voudrions.

De plus, rendre toutes les variables atomiques est le genre de chose qui est susceptible de faire fonctionner un programme multithread pire que son homologue à filetage unique, ce qui rend encore plus important de tester les performances sur des programmes plus réalistes et de voir si vous gagne rien ou pas.

Je ne sais pas non plus si vous essayez de garder les threads cachés du programmeur node.js ou si vous prévoyez de les exposer à un moment donné, créant effectivement un nouveau dialecte de Javascript pour la programmation multithread. Les deux options sont potentiellement intéressantes, mais il semble que vous n'ayez même pas encore décidé laquelle vous visez.

Donc, pour le moment, vous demandez aux programmeurs d'envisager de passer d'un environnement à filetage unique à un tout nouvel environnement multithread qui n'a pas de solution au problème de synchronisation et aucune preuve qu'il améliore les performances du monde réel, et apparemment aucun plan pour résoudre ces problèmes.

C'est probablement pourquoi les gens ne vous prennent pas au sérieux.

2) La simplicité et la robustesse de la boucle d'événement unique est un avantage énorme.

Les programmeurs Javascript savent que le langage Javascript est "à l'abri" des conditions de concurrence et d'autres bogues extrêmement insidieux qui affligent toute programmation véritablement multithread. Le fait qu'ils aient besoin d'arguments solides pour les convaincre d'abandonner que la sécurité ne les rend pas fermés, cela les rend responsables.

À moins que vous ne puissiez en quelque sorte conserver cette sécurité, quiconque souhaitant passer à un nœud.js multithread serait probablement mieux de passer à une langue comme Go conçue à partir de zéro pour les applications multithread.

3) Javascript prend déjà en charge les "threads d'arrière-plan" (WebWorkers) et la programmation asynchrone sans exposer directement la gestion des threads au programmeur.

Ces fonctionnalités résolvent déjà de nombreux cas d'utilisation courants qui affectent les programmeurs Javascript dans le monde réel, sans renoncer à la sécurité de la boucle d'événement unique.

Avez-vous des cas d'utilisation spécifiques à l'esprit que ces fonctionnalités ne résolvent pas et que les programmeurs Javascript souhaitent une solution? Si c'est le cas, ce serait une bonne idée de présenter votre node.js multithread dans le contexte de ce cas d'utilisation spécifique.


P.S. Qu'est-ce qui pourrait convaincre moi d'essayer de passer à une implémentation node.js multithread?

Écrivez un programme non trivial dans Javascript/node.js qui, selon vous, bénéficierait d'un véritable multithreading. Effectuez des tests de performances sur cet exemple de programme dans le nœud normal et votre nœud multithread. Montrez-moi que votre version améliore considérablement les performances d'exécution, la réactivité et l'utilisation de plusieurs cœurs, sans introduire de bogues ni d'instabilité.

Une fois cela fait, je pense que vous verrez des gens beaucoup plus intéressés par cette idée.

72
Ixrec

Juste deviner ici pour démontrer un problème dans votre approche. Je ne peux pas le tester par rapport à l'implémentation réelle car il n'y a aucun lien nulle part ...

Je dirais que c'est parce que les invariants ne sont pas toujours exprimés par la valeur d'une variable, et "une variable" n'est pas suffisante pour être la portée d'un verrou dans le cas général. Par exemple, imaginez que nous avons un invariant qui a+b = 0 (solde d'une banque avec deux comptes). Les deux fonctions ci-dessous garantissent que l'invariant est toujours maintenu à la fin de chaque fonction (l'unité d'exécution dans JS à un seul thread).

function withdraw(v) {
  a -= v;
  b += v;
}
function deposit(v) {
  b -= v;
  a += v;
}

Maintenant, dans votre monde multithread, que se passe-t-il lorsque deux threads exécutent withdraw et deposit en même temps? Merci, Murphy ...

(Vous pourriez avoir du code qui traite + = et - = spécialement, mais ce n'est pas utile. À un certain moment, vous aurez un état local dans une fonction, et sans aucun moyen de 'verrouiller' deux variables en même temps, votre invariant être violé.)


EDIT: Si votre code est sémantiquement équivalent au code Go à https://Gist.github.com/thriqon/f94c10a45b7e0bf656781b0f4a07292a , mon commentaire est exact ;-)

16
thriqon

Il y a une dizaine d'années, Brendan Eich (l'inventeur de JavaScript) a écrit un essai intitulé Threads Suck , qui est certainement l'un des rares documents canoniques de la mythologie du design de JavaScript.

Que ce soit correct est une autre question, mais je pense que cela a eu une grande influence sur la façon dont la communauté JavaScript pense à la concurrence.

15
Erik Pukinskis

l'accès atomique ne se traduit pas par un comportement thread-safe.

Un exemple est lorsqu'une structure de données globale doit être invalide pendant une mise à jour, comme ressasser une table de hachage (lors de l'ajout d'une propriété à un objet par exemple) ou trier un tableau global. Pendant ce temps, vous ne pouvez autoriser aucun autre thread à accéder à la variable. Cela signifie essentiellement que vous devez détecter tous les cycles de lecture-mise à jour-écriture et les verrouiller. Si la mise à jour n'est pas anodine, cela se traduira par l'arrêt du territoire problématique.

Javascript a été monothread et sandbox depuis le début et tout le code est écrit en tenant compte de ces hypothèses.

Cela a un grand avantage en ce qui concerne les contextes isolés et permet d'exécuter 2 contextes distincts dans des threads différents. Cela signifie également que les personnes qui écrivent en javascript n'ont pas besoin de savoir comment gérer les conditions de concurrence et divers autres pièges multi-threads.

8
ratchet freak

Votre approche va-t-elle améliorer considérablement les performances?

Douteux. Vous devez vraiment le prouver.

Votre approche va-t-elle faciliter/accélérer l'écriture de code?

Certainement pas, le code multithread est beaucoup plus difficile à obtenir correctement que le code à thread unique.

Votre approche va-t-elle être plus robuste?

Les blocages, les conditions de course, etc. sont un cauchemar à corriger.

6
Phil Wright

Votre implémentation ne consiste pas seulement à introduire la concurrence, mais plutôt à introduire une manière spécifique d'implémenter la concurrence, c'est-à-dire la concurrence par état mutable partagé. Au cours de l'histoire, les gens ont utilisé ce type de concurrence, ce qui a conduit à de nombreux types de problèmes. Bien sûr, vous pouvez créer des programmes simples qui fonctionnent parfaitement avec l'utilisation de la simultanéité d'état mutable partagée, mais le véritable test de tout mécanisme n'est pas ce qu'il peut faire, mais peut-il évoluer à mesure que le programme devient complexe et de plus en plus de fonctionnalités sont ajoutées au programme. N'oubliez pas que le logiciel n'est pas une chose statique que vous créez une fois et que vous en avez terminé, mais qu'il continue d'évoluer au fil du temps et si un mécanisme ou un concept ne peut pas faire face à l'évolution du logiciel, il ne fera que créer de nouveaux problèmes et c'est exactement ce que l'histoire nous a appris sur la simultanéité de la mémoire mutable partagée.

Vous pouvez consulter d'autres modèles de simultanéité (ex: transmission de messages) pour vous aider à déterminer quels types d'avantages ces modèles offrent.

2
Ankur

C'est nécessaire. L'absence d'un mécanisme de concurrence de bas niveau dans le nœud js limite ses applications dans des domaines tels que les mathématiques et la bioinformatique, etc. En outre, la concurrence avec les threads n'est pas nécessairement en conflit avec le modèle de concordance par défaut utilisé dans le nœud. Il existe une sémantique bien connue pour le filetage dans un environnement avec une boucle d'événement principale, comme les frameworks ui (et nodejs), et ils sont certainement trop complexes pour la plupart des situations qu'ils ont encore des utilisations valides.

Bien sûr, votre application Web moyenne ne nécessitera pas de threads, mais essayez de faire quelque chose d'un peu moins conventionnel, et l'absence d'une primitive de concurrence sonore de bas niveau vous entraînera rapidement vers quelque chose d'autre qui l'offre.

1
dryajov

Je crois vraiment que c'est parce que c'est une idée différente et puissante. Vous allez à l'encontre des systèmes de croyances. Les choses deviennent acceptées ou populaires par le biais du réseau et non sur la base du mérite. De plus, personne ne veut s'adapter à une nouvelle pile. Les gens rejettent automatiquement les choses trop différentes.

Si vous pouvez trouver un moyen d'en faire un module npm normal qui semble peu probable, alors vous pouvez faire en sorte que certaines personnes l'utilisent.

0
Jason Livesay