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?
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?
Réponses:
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 out
sur 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, change
et 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 onclick
immé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 addEventListener
et 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 alert
et 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 out
dans 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 showModalDialog
sur 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 setInterval
fonction. Si vous écrivez un framework que vous avez l'intention d'utiliser dans des applications complexes, cela pourrait être une bonne chose. postMessage
espérons également apaiser la douleur de l'écriture de documents croisés à l'avenir.
blur
après que votre code a rendu le contrôle au navigateur.
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/setInterval
les é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.)
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:
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 .
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.
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
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. setTimeout
et setInterval
sont 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).
@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.
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.
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.
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.
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).
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)