Maison  >  Article  >  interface Web  >  Javascript est-il un langage multithread ?

Javascript est-il un langage multithread ?

青灯夜游
青灯夜游original
2022-02-18 17:55:093654parcourir

Javascript n'est pas un langage multithread, mais un langage monothread. JavaScript est un langage de script de navigateur et son interpréteur est monothread ; et l'objectif principal de JavaScript est d'interagir avec les utilisateurs et de faire fonctionner le DOM, ce qui détermine qu'il ne peut être que monothread, sinon cela entraînera des problèmes de synchronisation très complexes. .

Javascript est-il un langage multithread ?

L'environnement d'exploitation de ce tutoriel : système Windows 7, JavaScript version 1.8.5, ordinateur Dell G3.

javascript n'est pas un langage multithread, mais un langage monothread. Le langage JavaScript ne prend pas non plus en charge le multithreading car l'interpréteur JavaScript du navigateur est monothread.

Une caractéristique majeure du langage JavaScript est qu'il est monothread, ce qui signifie qu'il ne peut faire qu'une seule chose à la fois.

Alors, pourquoi JavaScript ne peut-il pas avoir plusieurs threads ? Cela peut améliorer l’efficacité.

JavaScript est monothread, selon son objectif. En tant que langage de script de navigateur, l'objectif principal de JavaScript est d'interagir avec les utilisateurs et de manipuler le DOM. Cela détermine qu'il ne peut être qu'un seul thread, sinon cela entraînera des problèmes de synchronisation très complexes.

Afin de profiter de la puissance de calcul des CPU multicœurs, HTML5 propose le standard Web Worker, qui permet aux scripts JavaScript de créer plusieurs threads, mais les threads enfants sont entièrement contrôlés par le thread principal et ne doivent pas faire fonctionner le DOM. . Par conséquent, cette nouvelle norme ne modifie pas la nature monothread de JavaScript.

File d'attente des tâches

Un seul thread signifie que toutes les tâches doivent être mises en file d'attente et que la tâche suivante ne sera pas exécutée tant que la tâche précédente n'est pas terminée. Si la tâche précédente prend beaucoup de temps, la tâche suivante devra attendre.

Si la file d'attente est due à une grande quantité de calculs et que le CPU est trop occupé, oubliez ça, mais souvent le CPU est inactif car le périphérique IO (périphérique d'entrée et de sortie) est très lent (comme les opérations Ajax lisant les données du réseau). Je dois attendre les résultats avant de continuer.

Les concepteurs du langage JavaScript ont réalisé qu'à ce stade, le thread principal peut ignorer complètement le périphérique IO, suspendre les tâches en attente et exécuter les tâches ultérieures en premier. Attendez que le périphérique IO renvoie le résultat, puis revenez en arrière et poursuivez l'exécution de la tâche suspendue.

Ainsi, toutes les tâches peuvent être divisées en deux types, l'une est une tâche synchrone (synchrone) et l'autre est une tâche asynchrone (asynchrone).

Les tâches synchrones font référence aux tâches mises en file d'attente pour exécution sur le thread principal. La tâche suivante ne peut être exécutée qu'après l'exécution de la tâche précédente.

Les tâches asynchrones font référence aux tâches qui n'entrent pas dans le thread principal mais entrent dans la « file d'attente des tâches » ; " ( File d'attente des tâches), ce n'est que lorsque la "file d'attente des tâches" informe le thread principal qu'une tâche asynchrone peut être exécutée que la tâche entrera dans le thread principal pour exécution.

Plus précisément, le mécanisme de fonctionnement de l'exécution asynchrone est le suivant. (Il en va de même pour l'exécution synchrone, car elle peut être considérée comme une exécution asynchrone sans tâches asynchrones.)

(1) Toutes les tâches synchrones sont exécutées sur le thread principal, formant une pile de contexte d'exécution.

(2) En plus du fil de discussion principal, il existe également une "file d'attente des tâches". Tant que la tâche asynchrone a des résultats en cours d'exécution, un événement est placé dans la « file d'attente des tâches ».

(3) Une fois que toutes les tâches de synchronisation dans la « pile d'exécution » sont terminées, le système lira la « file d'attente des tâches » pour voir quels événements s'y trouvent. Les tâches asynchrones correspondantes mettent fin à l'état d'attente, entrent dans la pile d'exécution et démarrent l'exécution.

(4) Le fil principal continue de répéter la troisième étape ci-dessus.

L'image ci-dessous est un diagramme schématique du thread principal et de la file d'attente des tâches.

Javascript est-il un langage multithread ?

Tant que le thread principal est vide, il lira la "file d'attente des tâches". C'est le mécanisme d'exécution de JavaScript. Ce processus ne cesse de se répéter.

Événements et fonctions de rappel

La "file d'attente des tâches" est une file d'attente d'événements (peut également être comprise comme une file d'attente de messages). Lorsque le périphérique IO termine une tâche, un événement est ajouté à la "file d'attente des tâches" pour. représenter les tâches asynchrones pertinentes peuvent entrer dans la « pile d'exécution ». Le thread principal lit la "file d'attente des tâches", ce qui signifie lire les événements qu'elle contient.

Les événements de la « File d'attente des tâches », en plus des événements du périphérique IO, incluent également certains événements générés par l'utilisateur (tels que les clics de souris, le défilement de page, etc.). Tant que la fonction de rappel est spécifiée, ces événements entreront dans la « file d'attente des tâches » lorsqu'ils se produiront, en attendant que le thread principal les lise.

La soi-disant « fonction de rappel » (callback) est le code qui sera suspendu par le thread principal. Les tâches asynchrones doivent spécifier une fonction de rappel. Lorsque le thread principal commence à exécuter une tâche asynchrone, la fonction de rappel correspondante est exécutée.

La "file d'attente des tâches" est une structure de données premier entré, premier sorti. Les événements classés en premier sont lus en premier par le thread principal. Le processus de lecture du thread principal est fondamentalement automatique. Dès que la pile d'exécution est effacée, le premier événement de la « file d'attente des tâches » entrera automatiquement dans le thread principal. Cependant, en raison de la fonction "timer" mentionnée plus loin, le thread principal doit d'abord vérifier le temps d'exécution. Certains événements ne peuvent revenir au thread principal qu'après le temps spécifié.

Boucle d'événement

Le thread principal lit les événements de la « file d'attente des tâches ». Ce processus est cyclique, donc l'ensemble du mécanisme de fonctionnement est également appelé Event Loop.

Pour mieux comprendre Event Loop, veuillez regarder l'image ci-dessous.

Javascript est-il un langage multithread ?

Dans l'image ci-dessus, lorsque le thread principal est en cours d'exécution, un tas et une pile sont générés. Le code dans la pile appelle diverses API externes et ajoutent divers événements (clic, chargement, terminé). Tant que le code de la pile est exécuté, le thread principal lira la « file d'attente des tâches » et exécutera les fonctions de rappel correspondant à ces événements dans l'ordre.

Le code dans la pile d'exécution (tâche synchrone) est toujours exécuté avant de lire la "file d'attente des tâches" (tâche asynchrone). Jetez un œil à l’exemple ci-dessous.

    var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    
    req.onerror = function (){};    
    req.send();

La méthode req.send dans le code ci-dessus est une opération Ajax pour envoyer des données au serveur. Il s'agit d'une tâche asynchrone, ce qui signifie que le système ne lira pas la "file d'attente des tâches" avant d'avoir tous les codes du script actuel. sont exécutés. Cela équivaut donc à l’écriture suivante.

 var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};    
    req.onerror = function (){};

En d'autres termes, peu importe que la partie qui spécifie la fonction de rappel (onload et onerror) soit avant ou après la méthode send(), car elle fait partie de la pile d'exécution et le système s'exécutera toujours avant de les lire.

Timer

En plus de placer des événements pour les tâches asynchrones, la "file d'attente des tâches" peut également placer des événements chronométrés, c'est-à-dire spécifier la durée pendant laquelle certains codes seront exécutés après. C'est ce qu'on appelle la fonction « timer », qui est du code exécuté régulièrement.

La fonction timer est principalement complétée par les deux fonctions setTimeout() et setInterval(). Leurs mécanismes de fonctionnement internes sont exactement les mêmes. La différence est que le code spécifié par la première est exécuté une seule fois, tandis que la seconde est exécutée à plusieurs reprises. Ce qui suit traite principalement de setTimeout().

setTimeout() accepte deux paramètres, le premier est la fonction de rappel et le second est le nombre de millisecondes pour retarder l'exécution.

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

Les résultats d'exécution du code ci-dessus sont 1, 3, 2, car setTimeout() retarde l'exécution de la deuxième ligne jusqu'à 1000 millisecondes plus tard.

Si le deuxième paramètre de setTimeout() est défini sur 0, cela signifie qu'une fois le code actuel exécuté (la pile d'exécution est effacée), la fonction de rappel spécifiée sera exécutée immédiatement (intervalle de 0 milliseconde)

 setTimeout(function(){console.log(1);}, 0);
console.log(2);

L'exécution le résultat du code ci-dessus est toujours 2, 1, car ce n'est qu'après l'exécution de la deuxième ligne que le système exécutera la fonction de rappel dans la "file d'attente des tâches".

En bref, la signification de setTimeout(fn,0) est de spécifier une tâche à exécuter dans le premier temps d'inactivité disponible du thread principal, c'est-à-dire à exécuter le plus tôt possible. Il ajoute un événement à la fin de la « file d'attente des tâches », il ne sera donc exécuté que lorsque la tâche de synchronisation et les événements existants dans la « file d'attente des tâches » auront été traités.

La norme HTML5 stipule que la valeur minimale (intervalle le plus court) du deuxième paramètre de setTimeout() ne doit pas être inférieure à 4 millisecondes. Si elle est inférieure à cette valeur, elle augmentera automatiquement. Avant cela, les anciens navigateurs fixaient l'intervalle minimum à 10 millisecondes. De plus, ces modifications du DOM (en particulier celles impliquant un nouveau rendu de page) ne sont généralement pas exécutées immédiatement, mais toutes les 16 millisecondes. À l'heure actuelle, l'effet de l'utilisation de requestAnimationFrame() est meilleur que celui de setTimeout().

Il convient de noter que setTimeout() insère uniquement l'événement dans la "file d'attente des tâches". Le thread principal doit attendre que le code actuel (pile d'exécution) ait fini de s'exécuter avant que le thread principal exécute la fonction de rappel qu'il spécifie. Si le code actuel prend beaucoup de temps, cela peut prendre beaucoup de temps, il n'y a donc aucun moyen de garantir que la fonction de rappel sera exécutée à l'heure spécifiée par setTimeout().

Event Loop de Node.js

Node.js est également une boucle d'événement à thread unique, mais son mécanisme de fonctionnement est différent de l'environnement du navigateur.

Veuillez regarder le schéma ci-dessous

Javascript est-il un langage multithread ?Selon le schéma ci-dessus, le mécanisme de fonctionnement de Node.js est le suivant.

(1) Le moteur V8 analyse les scripts JavaScript.

(2) Le code analysé appelle l'API Node.

(3) La bibliothèque libuv est responsable de l'exécution de l'API Node. Il alloue différentes tâches à différents threads pour former une boucle d'événement (boucle d'événement), et renvoie les résultats d'exécution des tâches au moteur V8 de manière asynchrone.

(4) Le moteur V8 renvoie les résultats à l'utilisateur.

En plus des deux méthodes setTimeout et setInterval, Node.js fournit également deux autres méthodes liées à la « file d'attente des tâches » : process.nextTick et setImmediate. Ils peuvent nous aider à approfondir notre compréhension de la « file d’attente des tâches ».

La méthode process.nextTick peut déclencher la fonction de rappel à la fin de la "pile d'exécution" actuelle, avant la prochaine boucle d'événements (le thread principal lit la "file d'attente des tâches"). Autrement dit, la tâche spécifiée se produit toujours avant toutes les tâches asynchrones. La méthode setImmediate ajoute un événement à la fin de la « file d'attente des tâches » actuelle, c'est-à-dire que la tâche qu'elle spécifie est toujours exécutée dans la prochaine boucle d'événements, ce qui est très similaire à setTimeout(fn, 0). Voir l'exemple ci-dessous (via StackOverflow).

 process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});
 
setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED

上面代码中,由于process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前”执行栈”执行。

现在,再看setImmediate。

 setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});
 
setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2。

令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

 setImmediate(function (){
  setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
  });
 
  setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
  }, 0);
});
// 1
// TIMEOUT FIRED
// 2

上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1–TIMEOUT FIRED–2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。

我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!

 process.nextTick(function foo() {
  process.nextTick(foo);
});

事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。

另外,由于process.nextTick指定的回调函数是在本次”事件循环”触发,而setImmediate指定的是在下次”事件循环”触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查”任务队列”)。

【相关推荐:javascript学习教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn