web-dev-qa-db-fra.com

JavaScript est-il garanti d'être mono-thread?

JavaScript est connu pour être mono-thread dans toutes les implémentations de navigateur modernes, mais cela est-il spécifié dans un standard ou est-ce juste par tradition? Est-il totalement prudent de supposer que JavaScript est toujours à thread unique?

581
Egor Pavlikhin

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

JavaScript est généralement considéré comme ayant un seul fil d'exécution visible pour les scripts (*). Ainsi, lorsque vous entrez votre script en ligne, votre écouteur d'événement ou votre délai d'attente, vous gardez le contrôle total jusqu'à votre retour à la fin de votre bloc ou de votre fonction.

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

Cependant, en réalité ceci ce n'est pas tout à fait vrai, de manière sournoise et méchante.

Le cas le plus fréquent est celui des événements immédiats. Les navigateurs les activeront immédiatement lorsque votre code leur causera quelque chose:

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 dans log in, blur, log out sur tous sauf IE. Ces événements ne se déclenchent pas simplement parce que vous avez appelé focus() directement, ils peuvent se produire parce que vous avez appelé alert(), ou vous avez ouvert une fenêtre contextuelle ou tout autre élément déplaçant le focus.

Cela peut également entraîner d'autres événements. Par exemple, ajoutez un écouteur i.onchange et saisissez quelque chose dans l'entrée précédant l'appel focus(), et l'ordre du journal est log in, change, blur, log out, sauf dans Opera où il s'agit de log in, blur, log out, change et IE où il est (encore moins explicitement) log in, change, log out, blur.

De même, appeler click() sur un élément qui le fournit appelle immédiatement le gestionnaire onclick dans tous les navigateurs (du moins, cela est cohérent!).

(J'utilise les propriétés directes du gestionnaire d'événements on... ici, mais il en va de même avec addEventListener et attachEvent.)

Il existe également de nombreuses circonstances dans lesquelles des événements peuvent se déclencher pendant que votre code est inséré, malgré ce que vous avez fait rien 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>

Appuyez sur alert pour obtenir une boîte de dialogue modale. Aucun autre script n'est exécuté jusqu'à ce que vous ayez rejeté ce dialogue, oui? Nan. Redimensionnez la fenêtre principale et vous obtiendrez alert in, resize, alert out dans la zone de texte.

Vous penserez peut-être qu'il est impossible de redimensionner une fenêtre tant 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 de fois 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 résolution plus grande à une résolution plus petite où la fenêtre ne s'adapte pas, ce qui la redimensionnera.

Vous pourriez penser, eh bien, ce n’est que resize (et probablement quelques autres comme scroll) qui peut se déclencher lorsque l’utilisateur n’a pas d’interaction active avec le navigateur car le script est threadé. Et pour les fenêtres simples, vous avez peut-être raison. Mais tout cela va au pot dès que vous faites du script inter-fenêtre. 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 s'exécutant dans un thread d'exécution distinct et en forçant les gestionnaires d'événements associés à Feu.

Les endroits où les événements que vous pouvez provoquer sont générés peuvent être levés tant que le script est toujours en cours d'exécution:

  • lorsque les fenêtres contextuelles modales (alert, confirm, Prompt) sont ouvertes, dans tous les navigateurs sauf Opera;

  • pendant showModalDialog sur les navigateurs qui le prennent en charge;

  • la boîte de dialogue "Un script sur cette page est peut-être occupé ...", même si vous choisissez de laisser le script continuer à s'exécuter, permet à des é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'une opération. boucle occupée, sauf dans Opera.

  • il y a quelque temps, dans IE avec le plug-in Sun Java, l'appel de n'importe quelle méthode sur une applet pouvait permettre le déclenchement d'événements et le réenregistrement de scripts. C'était toujours un bug sensible au timing, et il est possible que Sun l'ait corrigé depuis (je l'espère bien).

  • probablement plus. Cela fait un moment que j'ai testé cela et les navigateurs ont gagné en complexité depuis.

En résumé, pour la plupart des utilisateurs, la plupart des utilisateurs de JavaScript ont un seul thread d'exécution strict. En réalité, cela n’existe pas. Il n’est pas clair qu’il s’agisse là simplement d’un bogue et d’une conception délibérée, mais si vous écrivez des applications complexes, en particulier des applications utilisant plusieurs fenêtres/scripts, il ya toutes les chances que cela puisse vous piquer - et de façon intermittente, des moyens difficiles à déboguer.

Si le pire se produit, vous pouvez résoudre les problèmes de simultanéité en indirectant toutes les réponses aux événements. Lorsqu'un événement arrive, déposez-le dans une file d'attente et gérez-le ultérieurement, dans une fonction setInterval. Si vous écrivez un cadre que vous avez l'intention d'utiliser pour des applications complexes, cela pourrait être une bonne chose. postMessage soulagera, espérons-le, la douleur des scripts entre documents à l'avenir.

558
bobince

Je dirais que oui, car pratiquement tous les codes javascript existants (du moins non triviaux) se briseraient si le moteur javascript du navigateur s'exécutait de manière asynchrone.

Ajoutez à cela le fait que HTML5 spécifie déjà Web Workers (une API explicite et normalisée pour le code javascript multi-threading) introduisant le multi-thread dans le langage JavaScript de base serait quasiment inutile.

( Note aux autres commentateurs: Même si setTimeout/setInterval, événements sur demande HTTP (XHR) et événements d'interface utilisateur (clic, focus, etc.) .) donnent une impression brute de multi-threadedness - ils sont toujours exécutés le long d’un seul scénario - un à la fois - donc, même si nous ne connaissons pas leur ordre d’exécution à l’avance, il n’est pas nécessaire de s’inquiéter des conditions externes exécution d'un gestionnaire d'événements, d'une fonction temporisée ou d'un rappel XHR.)

112
Már Örlygsson

Oui, même si vous rencontrez toujours des problèmes de programmation simultanée (principalement des conditions de concurrence) lorsque vous utilisez l'une des API asynchrones telles que les rappels setInterval et xmlhttp.

16
spender

Oui, même si Internet Explorer 9 compilera votre Javascript sur un thread séparé en vue de son exécution sur le thread principal. Cela ne change rien pour vous en tant que programmeur, cependant.

10
ChessWhiz

JavaScript/ECMAScript est conçu pour vivre dans un environnement hôte. C’est-à-dire que JavaScript ne fait réellement rien d’autre que si l’environnement de l’hôte décide d’analyser et d’exécuter un script donné et de fournir des objets d’environnement qui permettent à JavaScript d’être réellement. utile (comme le DOM dans les navigateurs).

Je pense qu'une fonction ou un bloc de script donné s'exécutera ligne par ligne, ce qui est garanti pour JavaScript. Cependant, un environnement hôte pourrait peut-être exécuter plusieurs scripts en même temps. Ou bien, un environnement hôte pourrait toujours fournir un objet fournissant plusieurs threads. setTimeout et setInterval sont des exemples, ou tout au moins de pseudo-exemples, d'un environnement hôte offrant un moyen d'effectuer une concurrence simultanée (même s'il ne s'agit pas exactement d'une concurrence simultanée).

7
Bob

En fait, une fenêtre parente peut communiquer avec des fenêtres ou des cadres enfants ou frères et sœurs ayant leurs propres threads d'exécution en cours d'exécution.

7
kennebec

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

Je pense que le paradigme monobloc non bloquant est né de la nécessité d'exécuter JavaScript dans des navigateurs où l'interface utilisateur ne devrait jamais bloquer.

Nodejs a suivi l'approche des navigateurs.

Rhino moteur cependant, supporte 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 entre les threads, mais ne donne aucune autre garantie pour les scripts exécutés simultanément dans la même étendue. Si deux scripts utilisent simultanément la même étendue, les scripts sont responsables de la coordination des accès aux variables partagées. "

En lisant la documentation de Rhino, je conclus qu’il peut être possible d’écrire une api javascript qui génère également de nouveaux fils javascript, mais que cette api est spécifique à Rhino (par exemple, un nœud ne peut générer un nouveau processus).

J'imagine que même pour un moteur prenant en charge plusieurs threads en javascript, il devrait exister une compatibilité avec des scripts ne prenant pas en compte le multi-threading ou le blocage.

Voici comment je vois les navigateurs et les nœuds comme suit:

  • Est-ce que tout le code js est exécuté dans un seul thread? : Oui.

  • Le code js peut-il provoquer l'exécution d'autres threads? : Oui.

  • Ces threads peuvent-ils muter le contexte d'exécution js?: Non. Mais ils peuvent (directement/indirectement (?)) S'ajouter à la file d'attente des événements.

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

6
Marinos An

@Bobince fournit une réponse vraiment opaque.

Pour reprendre la réponse de Már Örlygsson, javascript est toujours un seul thread à cause de ce simple fait: tout ce qui est javascript est exécuté le long d’un seul scénario.

C'est la définition stricte d'un langage de programmation mono-thread.

6
williamle8300

Non.

Je vais contre la foule ici, mais supporte-moi. Un seul script JS est destiné à être effectivement single threaded, 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 dans l’espoir qu’à la fin de la boucle, la liste doit contenir 10000 entrées qui sont l’indice au carré, mais le VM peut 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 sécurisée pour les threads, ils seront exécutés au moment voulu. le même résultat que la version à un seul thread.

Bien que, à ma connaissance, aucune VM ne détecte un tel code parallélisable, il semble probable qu'il puisse exister à l'avenir pour les machines virtuelles JIT, car il pourrait offrir plus de rapidité dans certaines situations.

En prenant ce concept plus loin, il est possible que le code soit annoté pour permettre à la VM 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;
}

Depuis que les travailleurs Web utilisent Javascript, il est peu probable que ce ... système plus laid existe, mais je pense qu'il est prudent de dire que Javascript est mono-threadé par tradition.

4
max

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-threaded".

Il n’existe aucun support en Javascript pour le multi-threading, du moins de manière non explicite, cela ne fait donc aucune différence.

3
Francisco Soto

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 l'alerte contextuelle 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 de Firefox sous Windows stable et que vous réduisez/maximisez la fenêtre avec une alerte contextuelle à l'écran, il se passera à peu près 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 avait été exécuté à un moment inopportun, et je ne m'attendais pas à cela, et j'ai maintenant un état corrompu. Et il vaut mieux connaître ce comportement.

2
Anton Alexandrenok