JavaScript est-il garanti d'être monothread?

611

JavaScript est connu pour être monothread dans toutes les implémentations de navigateur modernes, mais est-ce spécifié dans une norme ou est-ce juste par tradition? Est-il totalement sûr de supposer que JavaScript est toujours monothread?

Egor Pavlikhin
la source
25
Dans le contexte des navigateurs, probablement. Mais certains programmes vous permettent de traiter JS comme une langue de niveau supérieur et de fournir des liaisons pour d'autres bibliothèques C ++. Par exemple, flusspferd (liaisons C ++ pour JS - AWESOME BTW) faisait des choses avec JS multithread. Cela dépend du contexte.
NG.
11
Ceci est une lecture incontournable: developer.mozilla.org/en/docs/Web/JavaScript/EventLoop
RickyA

Réponses:

583

C'est une bonne question. J'adorerais dire "oui". Je ne peux pas.

JavaScript est généralement considéré comme ayant un seul thread d'exécution visible par les scripts (*), de sorte que lorsque votre script en ligne, l'écouteur d'événements ou le délai d'expiration est entré, vous gardez un contrôle total jusqu'à ce que vous reveniez de la fin de votre bloc ou de votre fonction.

(*: ignorer la question de savoir si les navigateurs implémentent réellement leurs moteurs JS à l'aide d'un thread OS, ou si d'autres threads d'exécution limités sont introduits par WebWorkers.)

Cependant, en réalité, ce n'est pas tout à fait vrai , de façon méchante et sournoise.

Le cas le plus courant est celui des événements immédiats. Les navigateurs les déclenchent immédiatement lorsque votre code fait quelque chose pour les provoquer:

var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

Résultats log in, blur, log outsur tous sauf IE. Ces événements ne se déclenchent pas uniquement parce que vous avez appelé focus()directement, ils peuvent se produire parce que vous avez appelé alert()ou ouvert une fenêtre contextuelle ou tout autre élément qui déplace le focus.

Cela peut également entraîner d'autres événements. Par exemple, ajoutez un i.onchangeécouteur et tapez quelque chose dans l'entrée avant que l' focus()appel ne le mette au point, et l'ordre du journal est log in, change, blur, log out, sauf dans Opera où il se trouve log in, blur, log out, changeet IE où il se trouve (encore moins de manière explicite) log in, change, log out, blur.

De même, faire appel click()à un élément qui le fournit appelle onclickimmédiatement le gestionnaire dans tous les navigateurs (au moins c'est cohérent!).

(J'utilise on...ici les propriétés du gestionnaire d'événements direct , mais la même chose se produit avec addEventListeneret attachEvent.)

Il y a aussi un tas de circonstances dans lesquelles des événements peuvent se déclencher pendant que votre code est enfilé, bien que vous n'ayez rien fait pour le provoquer. Un exemple:

var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

Frappez alertet vous obtiendrez une boîte de dialogue modale. Plus aucun script ne s'exécute jusqu'à ce que vous fermiez ce dialogue, oui? Nan. Redimensionnez la fenêtre principale et vous obtiendrez alert in, resize, alert outdans la zone de texte.

Vous pourriez penser qu'il est impossible de redimensionner une fenêtre alors qu'une boîte de dialogue modale est ouverte, mais ce n'est pas le cas: sous Linux, vous pouvez redimensionner la fenêtre autant que vous le souhaitez; sous Windows, ce n'est pas si facile, mais vous pouvez le faire en changeant la résolution de l'écran d'une plus grande à une plus petite où la fenêtre ne rentre pas, ce qui provoque son redimensionnement.

Vous pourriez penser, eh bien, ce n'est que resize(et probablement quelques autres scroll) que peut se déclencher lorsque l'utilisateur n'a pas d'interaction active avec le navigateur car le script est enfilé. Et pour les guichets uniques, vous avez peut-être raison. Mais tout va au pot dès que vous effectuez des scripts inter-fenêtres. Pour tous les navigateurs autres que Safari, qui bloque toutes les fenêtres / onglets / cadres lorsque l'un d'entre eux est occupé, vous pouvez interagir avec un document à partir du code d'un autre document, en exécutant dans un thread d'exécution distinct et en provoquant la gestion de tous les gestionnaires d'événements associés. Feu.

Les endroits où les événements que vous pouvez faire générer peuvent être déclenchés pendant que le script est toujours enfilé:

  • lorsque les popups modales ( alert, confirm, prompt) sont ouverts, dans tous les navigateurs , mais Opera;

  • pendant showModalDialogsur les navigateurs qui le prennent en charge;

  • la boîte de dialogue "Un script sur cette page peut être occupé ...", même si vous choisissez de laisser le script continuer à s'exécuter, permet aux événements tels que le redimensionnement et le flou de se déclencher et d'être gérés même lorsque le script est au milieu d'un boucle occupée, sauf dans Opera.

  • il y a quelque temps pour moi, dans IE avec le plugin Sun Java, appeler n'importe quelle méthode sur une applet pouvait permettre aux événements de se déclencher et de réécrire le script. C'était toujours un bug sensible au timing, et il est possible que Sun l'ait corrigé depuis (je l'espère certainement).

  • probablement plus. Cela fait un moment que je n'ai pas testé cela et les navigateurs sont devenus plus complexes depuis.

En résumé, JavaScript apparaît à la plupart des utilisateurs, la plupart du temps, comme ayant un thread d'exécution unique piloté par les événements. En réalité, il n'a rien de tel. On ne sait pas exactement dans quelle mesure il s'agit simplement d'un bogue et de la conception délibérée, mais si vous écrivez des applications complexes, en particulier des applications cross-window / frame-scripting, il y a toutes les chances que cela puisse vous mordre - et de manière intermittente, méthodes difficiles à déboguer.

Si le pire arrive au pire, vous pouvez résoudre les problèmes de concurrence en indirectant toutes les réponses aux événements. Lorsqu'un événement arrive, déposez-le dans une file d'attente et traitez-la dans l'ordre plus tard, dans une setIntervalfonction. Si vous écrivez un framework que vous avez l'intention d'utiliser dans des applications complexes, cela pourrait être une bonne chose. postMessageespérons également apaiser la douleur de l'écriture de documents croisés à l'avenir.

bobince
la source
14
@JP: Personnellement, je ne veux pas savoir tout de suite parce que cela signifie que je dois faire attention à ce que mon code soit rentrant, que l'appel de mon code flou n'affecte pas l'état sur lequel certains codes externes comptent. Il y a trop de cas où le flou est un effet secondaire inattendu pour nécessairement attraper tout le monde. Et malheureusement, même si vous le souhaitez, ce n'est pas fiable! IE se déclenche blur après que votre code a rendu le contrôle au navigateur.
bobince
107
Javascript est un thread unique. Arrêter votre exécution sur alert () ne signifie pas que le thread d'événements arrête de pomper les événements. Cela signifie simplement que votre script est en veille pendant que l'alerte est à l'écran, mais il doit continuer à pomper les événements afin de dessiner l'écran. Pendant qu'une alerte est déclenchée, la pompe d'événements fonctionne, ce qui signifie qu'il est tout à fait correct de continuer à envoyer des événements. Au mieux, cela démontre un filetage coopératif qui peut se produire en javascript, mais tout ce comportement pourrait être expliqué par une fonction ajoutant simplement un événement à la pompe d'événements pour être traité ultérieurement par rapport à le faire maintenant.
chubbsondubs
34
Mais rappelez-vous que le thread coopératif est toujours un thread unique. Deux choses ne peuvent pas se produire simultanément, ce que le multi-threading permet et injecte du non-déterminisme. Tout ce qui a été décrit est déterministe, et c'est un bon rappel sur ces types de problèmes. Bon travail sur l'analyse @bobince
chubbsondubs
94
Chubbard a raison: JavaScript est un thread unique. Ce n'est pas un exemple de multithreading, mais plutôt une répartition synchrone des messages dans un seul thread. Oui, il est possible de suspendre la pile et de poursuivre la répartition des événements (par exemple alert ()), mais les types de problèmes d'accès qui se produisent dans de véritables environnements multithread ne peuvent tout simplement pas se produire; par exemple, vous n'aurez jamais de variable sur vous entre un test et une affectation immédiatement suivante, car votre thread ne peut pas être interrompu arbitrairement. Je crains que cette réponse ne fasse que semer la confusion.
Kris Giesing
19
Oui, mais étant donné qu'une fonction de blocage qui attend la saisie de l'utilisateur peut se produire entre deux instructions, vous avez potentiellement tous les problèmes de cohérence que les threads au niveau du système d'exploitation vous posent. Que le moteur JavaScript s'exécute réellement sur plusieurs threads du système d'exploitation est peu pertinent.
bobince
115

Je dirais que oui - parce que pratiquement tout le code javascript existant (au moins tout non trivial) se briserait si le moteur javascript d'un navigateur l'exécutait de manière asynchrone.

Ajoutez à cela le fait que HTML5 spécifie déjà les Web Workers (une API explicite et standardisée pour le code javascript multi-threading) introduisant le multi-threading dans le Javascript de base serait presque inutile.

( Remarque pour les autres commentateurs: même si setTimeout/setIntervalles événements de chargement de requête HTTP (XHR) et les événements d'interface utilisateur (clic, focus, etc.) fournissent une impression grossière de multithread - ils sont toujours tous exécutés sur une seule chronologie - une à un temps - donc même si nous ne connaissons pas leur ordre d'exécution à l'avance, il n'y a pas besoin de s'inquiéter des conditions externes changeant pendant l'exécution d'un gestionnaire d'événements, d'une fonction temporisée ou d'un rappel XHR.)

Már Örlygsson
la source
21
Je suis d'accord. Si le multithread est jamais ajouté à Javascript dans le navigateur, ce sera via une API explicite (par exemple, Web Workers), comme c'est le cas avec tous les langages impératifs. C'est la seule façon qui ait du sens.
Dean Harding
1
Notez qu'il y en a un seul. thread JS principal, MAIS certaines choses sont exécutées en parallèle dans le navigateur. Ce n'est pas seulement une impression de multi-thread. Les demandes sont en fait exécutées en parallèle. Les écouteurs que vous définissez dans JS sont exécutés un par un, mais les demandes sont vraiment parallèles.
Nux
16

Oui, bien que vous puissiez toujours souffrir de certains problèmes de programmation simultanée (principalement les conditions de concurrence) lors de l'utilisation des API asynchrones telles que les rappels setInterval et xmlhttp.

dépensier
la source
10

Oui, bien qu'Internet Explorer 9 compilera votre Javascript sur un thread séparé en préparation de l'exécution sur le thread principal. Cependant, cela ne change rien pour vous en tant que programmeur.

ChessWhiz
la source
8

Je dirais que la spécification n'empêche pas quelqu'un de créer un moteur qui exécute javascript sur plusieurs threads , nécessitant le code pour effectuer la synchronisation pour accéder à l'état d'objet partagé.

Je pense que le paradigme non bloquant à un seul thread est né de la nécessité d'exécuter javascript dans les navigateurs où l'interface utilisateur ne devrait jamais bloquer.

Nodejs a suivi l' approche des navigateurs .

Cependant, le moteur Rhino prend en charge l'exécution de code js dans différents threads . Les exécutions ne peuvent pas partager le contexte, mais elles peuvent partager la portée. Pour ce cas spécifique, la documentation indique:

... "Rhino garantit que les accès aux propriétés des objets JavaScript sont atomiques sur les threads, mais ne garantit plus les scripts s'exécutant dans la même étendue en même temps. Si deux scripts utilisent la même étendue simultanément, les scripts sont responsable de la coordination des accès aux variables partagées . "

En lisant la documentation de Rhino, je conclus qu'il peut être possible pour quelqu'un d'écrire une API javascript qui génère également de nouveaux threads javascript, mais l'api serait spécifique aux rhinocéros (par exemple, le nœud ne peut générer qu'un nouveau processus).

J'imagine que même pour un moteur qui prend en charge plusieurs threads en javascript, il devrait y avoir une compatibilité avec des scripts qui ne considèrent pas le multi-thread ou le blocage.

Concearning navigateurs et nodejs la façon dont je le vois est:

    1. Est -ce tout js code exécuté dans un seul thread ? : Oui.
    1. Le code js peut-il entraîner l' exécution d'autres threads ? : Oui.
    1. Ces threads peuvent-ils muter le contexte d'exécution js ?: Non. Mais ils peuvent (directement / indirectement (?)) S'ajouter à la file d' attente d'événements à partir de laquelle les écouteurs peuvent muter le contexte d'exécution . Mais ne vous y trompez pas, les auditeurs s'exécutent à nouveau atomiquement sur le thread principal .

Ainsi, dans le cas des navigateurs et des nœuds (et probablement de nombreux autres moteurs), javascript n'est pas multithread mais les moteurs eux-mêmes le sont .


Mise à jour sur les web-workers:

La présence de travailleurs du Web justifie en outre que javascript peut être multi-thread, dans le sens où quelqu'un peut créer du code en javascript qui s'exécutera sur un thread séparé.

Cependant: les web-workers ne s'attaquent pas aux problèmes des threads traditionnels qui peuvent partager le contexte d'exécution. Les règles 2 et 3 ci-dessus s'appliquent toujours , mais cette fois le code fileté est créé par l'utilisateur (rédacteur de code js) en javascript.

La seule chose à considérer est le nombre de threads générés, d'un point de vue efficacité (et non simultané ). Voir ci-dessous:

À propos de la sécurité des fils :

L'interface Worker génère de véritables threads au niveau du système d'exploitation, et les programmeurs conscients peuvent craindre que la simultanéité puisse provoquer des effets «intéressants» dans votre code si vous ne faites pas attention.

Cependant, comme les travailleurs du Web contrôlent soigneusement les points de communication avec d'autres threads, il est en fait très difficile de provoquer des problèmes de concurrence . Il n'y a pas d'accès aux composants non threadsafe ou au DOM. Et vous devez passer des données spécifiques dans et hors d'un thread via des objets sérialisés. Vous devez donc travailler très dur pour provoquer des problèmes dans votre code.


PS

En plus de la théorie, soyez toujours prêt à propos des éventuels cas d'angle et des bugs décrits sur la réponse acceptée

Marinos An
la source
7

JavaScript / ECMAScript est conçu pour vivre dans un environnement hôte. Autrement dit, JavaScript ne fait rien à moins que l'environnement hôte ne décide d'analyser et d'exécuter un script donné, et de fournir des objets d'environnement qui permettent à JavaScript d'être utile (comme le DOM dans les navigateurs).

Je pense qu'une fonction ou un bloc de script donné s'exécutera ligne par ligne et cela est garanti pour JavaScript. Cependant, un environnement hôte pourrait peut-être exécuter plusieurs scripts en même temps. Ou, un environnement hôte peut toujours fournir un objet qui fournit le multithread. setTimeoutet setIntervalsont des exemples, ou du moins des pseudo-exemples, d'un environnement hôte fournissant un moyen de faire de la concurrence (même si ce n'est pas exactement la concurrence).

Bob
la source
7

En fait, une fenêtre parent peut communiquer avec des fenêtres ou des cadres enfants ou frères qui ont leurs propres threads d'exécution en cours d'exécution.

kennebec
la source
6

@Bobince fournit une réponse vraiment opaque.

S'écartant de la réponse de Már Örlygsson, Javascript est toujours monothread en raison de ce simple fait: tout en Javascript est exécuté le long d'une seule chronologie.

Telle est la définition stricte d'un langage de programmation monothread.

wle8300
la source
4

Non.

Je vais contre la foule ici, mais restez avec moi. Un seul script JS est censé être un seul thread efficace , mais cela ne signifie pas qu'il ne peut pas être interprété différemment.

Disons que vous avez le code suivant ...

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Ceci est écrit avec l'espoir qu'à la fin de la boucle, la liste doit avoir 10000 entrées qui sont l'index au carré, mais la VM pourrait remarquer que chaque itération de la boucle n'affecte pas l'autre, et réinterpréter en utilisant deux threads.

Premier fil

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

Deuxième fil

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

Je simplifie ici, parce que les tableaux JS sont plus compliqués que des morceaux de mémoire stupides, mais si ces deux scripts sont capables d'ajouter des entrées au tableau de manière thread-safe, alors au moment où les deux sont terminés, il aura le même résultat que la version monothread.

Bien que je ne sois au courant d'aucune machine virtuelle détectant du code parallélisable comme celui-ci, il semble probable que cela pourrait voir le jour à l'avenir pour les machines virtuelles JIT, car il pourrait offrir plus de vitesse dans certaines situations.

En poussant ce concept plus loin, il est possible que le code puisse être annoté pour permettre à la machine virtuelle de savoir quoi convertir en code multithread.

// like "use strict" this enables certain features on compatible VMs.
"use parallel";

var list = [];

// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Étant donné que les travailleurs du Web arrivent à Javascript, il est peu probable que ce ... système plus laid puisse jamais exister, mais je pense qu'il est sûr de dire que Javascript est unidirectionnel par tradition.

max
la source
2
Cependant, la plupart des définitions de langage sont conçues pour être effectivement monothread et indiquent que le multithreading est autorisé tant que l'effet est identique. (par exemple UML)
jevon
1
Je dois être d'accord avec la réponse simplement parce que l'ECMAScript actuel ne prévoit aucune disposition (bien que je pense sans doute que la même chose peut être dite pour C) pour les contextes d'exécution ECMAScript simultanés . Ensuite, comme cette réponse, je dirais que toute implémentation qui a des threads simultanés pouvant modifier un état partagé est une extension ECMAScript .
user2864740
3

Eh bien, Chrome est multiprocessus, et je pense que chaque processus traite de son propre code Javascript, mais pour autant que le code le sache, il est "mono-thread".

Il n'y a aucun support en Javascript pour le multi-threading, du moins pas explicitement, donc cela ne fait aucune différence.

Francisco Soto
la source
2

J'ai essayé l'exemple de @ bobince avec de légères modifications:

<html>
<head>
    <title>Test</title>
</head>
<body>
    <textarea id="log" rows="20" cols="40"></textarea>
    <br />
    <button id="act">Run</button>
    <script type="text/javascript">
        let l= document.getElementById('log');
        let b = document.getElementById('act');
        let s = 0;

        b.addEventListener('click', function() {
            l.value += 'click begin\n';

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end\n';
            l.value += `result = ${s}, should be ${s2 + s2}\n`;
            l.value += '----------\n';
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize\n';
        });
    </script>
</body>
</html>

Ainsi, lorsque vous appuyez sur Exécuter, fermez la fenêtre d'alerte et effectuez un "thread unique", vous devriez voir quelque chose comme ceci:

click begin
click end
result = 20, should be 20

Mais si vous essayez de l'exécuter dans Opera ou Firefox stable sous Windows et de minimiser / maximiser la fenêtre avec une fenêtre d'alerte à l'écran, il y aura quelque chose comme ceci:

click begin
resize
click end
result = 15, should be 20

Je ne veux pas dire que c'est du "multithreading", mais un morceau de code s'est exécuté au mauvais moment, sans m'attendre à cela, et maintenant j'ai un état corrompu. Et mieux connaître ce comportement.

Anton Alexandrenok
la source
-4

Essayez d'imbriquer deux fonctions setTimeout l'une dans l'autre et elles se comporteront en multithread (c'est-à-dire que le temporisateur externe n'attendra pas que la temporisation interne se termine avant d'exécuter sa fonction).

James
la source
5
Chrome fait cela dans le bon sens, ne sait pas où @James le voit être multithread ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(pour mémoire, yo dawg devrait TOUJOURS venir en premier, puis la sortie du journal de la console)
Tor Valamo