Maison >interface Web >js tutoriel >Multi-threading en javascript
D'accord, avant de commencer, laissez-moi venir propre et admettre que le titre de cet article est un peu sensationnaliste! JavaScript n'a pas vraiment de capacités multiples, et il n'y a rien qu'un programmeur JavaScript puisse faire pour changer cela. Dans tous les navigateurs - en dehors de Google Chrome - JavaScript s'exécute dans un seul fil d'exécution, et c'est comme ça.
Cependant, ce que nous pouvons faire est simuler multi-threading, dans la mesure où il donne naissance à l'un des avantages d'un environnement multi-thread: il nous permet d'exécuter un code extrêmement intensif. Il s'agit d'un code qui, autrement, gelerait le navigateur et générerait l'un de ces avertissements de «script non réactif» dans Firefox.
Tout dépend de l'utilisation des minuteries asynchrones. Lorsque nous exécutons du code répétitif dans une minuterie asynchrone, nous donnons à l'interprète du script du navigateur pour traiter chaque itération.
Effectivement, un morceau de code à l'intérieur d'un pour iterator demande à l'interprète de tout faire immédiatement: "Exécutez ce code n fois aussi vite que possible." Cependant, le même code à l'intérieur d'une minuterie asynchrone casse le code en petits morceaux discrets; Autrement dit, «Exécutez ce code une fois aussi rapidement possible» - puis attendez - puis «Exécutez ce code une fois aussi vite que possible», et ainsi de suite, n fois.
L'astuce est que le code à l'intérieur de chaque itération est suffisamment petit et simple pour que l'interprète le traite complètement à la vitesse de la minuterie, que ce soit 100 ou 5 000 millisecondes. Si cette exigence est satisfaite, alors peu importe à quel point le code global est intense, car nous ne demandons pas que ce soit exécuté en même temps.
Normalement, si j'écrivais un script qui s'est avéré trop intensif, je chercherais à le réingérer; Un tel ralentissement significatif indique généralement un problème avec le code, ou un problème plus profond avec la conception d'une application.
mais parfois non. Parfois, il n'y a tout simplement aucun moyen d'éviter l'intensité d'une opération particulière, à moins de ne pas le faire en javascript du tout.
Cela pourrait être la meilleure solution dans un cas donné; Peut-être que certains traitements dans une application doivent être déplacés vers le côté serveur, où il a plus de puissance de traitement pour travailler avec, en général, et un environnement d'exécution véritablement fileté (un serveur Web).
mais finalement vous trouverez peut-être une situation où ce n'est tout simplement pas une option - où JavaScript doit être être capable de faire quelque chose ou d'être damné. C'est la situation dans laquelle je me suis retrouvé lors du développement de mon extension Firefox, des sélecteurs de poussière.
Le cœur de cette extension est la possibilité de tester les sélecteurs CSS qui s'appliquent à une page, pour voir s'ils sont réellement utilisés. L'essence de ceci est un ensemble d'évaluations en utilisant la méthode Matchall () de Dean Edwards 'Base2:
for(var i=0; i<selectors.length; i++) <br> { <br> if(base2.DOM.Document.matchAll <br> (contentdoc, selectors[i]).length > 0) <br> { <br> used ++; <br> } <br> else <br> { <br> unused ++; <br> } <br> }
assez simple, c'est sûr. Mais Matchall () lui-même est assez intense, ayant - comme il le fait - pour analyser et évaluer n'importe quel sélecteur CSS1 ou CSS2, puis marcher sur l'ensemble de l'arbre Dom à la recherche de correspondances; Et l'extension fait cela pour chaque sélecteur individuel , dont il peut y avoir plusieurs milliers. Ce processus, en surface, si simple, pourrait être si intensif que tout le navigateur se fige pendant que cela se produit. Et c'est ce que nous trouvons.
Verrouiller le navigateur n'est évidemment pas une option, donc si cela fonctionne du tout, nous devons trouver un moyen de le faire fonctionner sans erreur.
Démontrer le problème avec un simple cas de test impliquant deux niveaux d'itération; Le niveau intérieur est délibérément trop intensif afin que nous puissions créer les conditions de course, tandis que le niveau extérieur est assez court pour qu'il simule le code principal. C'est ce que nous avons:
function process() <br> { <br> var above = 0, below = 0; <br> for(var i=0; i<200000; i++) <br> { <br> if(Math.random() * 2 > 1) <br> { <br> above ++; <br> } <br> else <br> { <br> below ++; <br> } <br> } <br> } <br> <br> <br> function test1() <br> { <br> var result1 = document.getElementById('result1'); <br> <br> var start = new Date().getTime(); <br> <br> for(var i=0; i<200; i++) <br> { <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [i=' + i + ']'; <br> <br> process(); <br> } <br> <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [done]'; <br> }
Nous lançons notre test, et obtenons notre sortie, à partir d'un formulaire simple (c'est le code de test, pas la production, alors pardonnez-moi de recourir à l'utilisation de gestionnaires d'événements en ligne):
<form action=""> <br> <fieldset> <br> <input type="button" value="test1" onclick="test1()" /> <br> <input type="text" /> <br> </fieldset> <br> </form> <br>
Maintenant, exécutons ce code dans Firefox (dans ce cas, Firefox 3 sur un MacBook 2 GHz)… et comme prévu, l'interface utilisateur du navigateur se fige pendant qu'il s'exécute (ce qui rend impossible, par exemple, pour appuyer de rafraîchir et d'abandonner le processus) . Après environ 90 itérations, Firefox produit un dialogue d'avertissement «script non réactif».
Si nous lui permettons de continuer, après 90 itérations, Firefox produit à nouveau la même boîte de dialogue.
Safari 3 et Internet Explorer 6 se comportent de la même manière à cet égard, avec une interface utilisateur congelée et un seuil auquel un dialogue d'avertissement est produit. Dans l'opéra, il n'y a pas de dialogue de ce type - il continue simplement d'exécuter le code jusqu'à ce qu'il soit fait - mais l'interface utilisateur du navigateur est également gelée jusqu'à ce que la tâche soit terminée.
clairement, nous ne pouvons pas exécuter du code comme ça dans la pratique. Alors, réaffirmons-le et utilisons une minuterie asynchrone pour la boucle extérieure:
for(var i=0; i<selectors.length; i++) <br> { <br> if(base2.DOM.Document.matchAll <br> (contentdoc, selectors[i]).length > 0) <br> { <br> used ++; <br> } <br> else <br> { <br> unused ++; <br> } <br> }
Maintenant, passons à nouveau… et cette fois, nous recevons des résultats complètement différents. Le code prend un certain temps à terminer, bien sûr, mais il fonctionne avec succès jusqu'à la fin, sans le gel de l'interface utilisateur et sans avertissements sur les scripts excessivement lents.
Afficher la page de test
(le drapeau occupé est utilisé pour empêcher les instances de minuterie de collision. Si nous sommes déjà au milieu d'un sous-processus lorsque la prochaine itération arrive, nous attendons simplement l'itération suivante, garantissant ainsi qu'un seul Le sous-processus fonctionne à la fois.)
Donc, vous voyez, bien que le travail que nous puissions faire sur le processus Inner est toujours minime, le nombre de fois Nous pouvons exécuter ce processus est maintenant illimité: nous pouvons exécuter le Boucle extérieure fondamentalement pour toujours, et le navigateur ne gèlera jamais.
C'est beaucoup plus comme ça - nous pouvons l'utiliser dans la nature.
Je peux déjà entendre les objecteurs. En fait, je pourrais être moi-même: pourquoi feriez-vous cela - quel genre de fou insiste pour pousser JavaScript dans tous ces endroits où il n'a jamais été conçu pour aller? Votre code est tout simplement trop intense. C'est le mauvais outil pour le travail. Si vous devez sauter dans ce type de cerceaux, la conception de votre application est fondamentalement erronée.
J'ai déjà mentionné un exemple où j'ai dû trouver un moyen pour que les scripts lourds fonctionnent; C'était soit cela, soit toute l'idée devait être abandonnée. Si vous n'êtes pas convaincu par cette réponse, le reste de l'article peut ne pas vous faire appel non plus.
Mais si vous l'êtes - ou du moins, si vous êtes ouvert à être convaincu, voici un autre exemple qui le cloue vraiment à la maison: en utilisant JavaScript pour écrire des jeux où vous pouvez jouer contre l'ordinateur.
Ce dont je parle ici, c'est le code requis pour comprendre les règles d'un jeu, qui peut ensuite évaluer les situations et les tactiques afin d'essayer de vous battre à ce jeu. Des trucs compliqués.
Pour illustrer, je vais regarder un projet que je développe depuis un petit moment. Par «petit temps», je veux dire trois ans , dont la majorité a été consacrée à un plateau où le jeu travaillait théoriquement, mais était trop intense pour être utilisé… jusqu'à ce que je pense à cette approche. Le jeu est un puzzle compétitif basé sur la correspondance de couleurs et de forme.
Pour résumer: vous vous frayez un chemin à tous les niveaux par la correspondance et les couleurs adjacentes. Par exemple, si vous commencez, par exemple, un triangle vert - vous pouvez vous déplacer vers tout autre triangle ou toute autre forme verte. Votre objectif est d'atteindre le cristal au milieu, puis de l'emmener de l'autre côté de la planche, tandis que votre adversaire essaie de faire de même. Vous pouvez également voler le cristal de votre adversaire.
Ainsi, nous avons des règles logiques déterminant le mouvement et nous pouvons également voir des tactiques émerger. Par exemple, pour éviter que votre adversaire atteigne le cristal ou vous le voler - vous pouvez sélectionner un mouvement qui les bloque, ou essayer de finir à un endroit où il ne peut pas atteindre.
Le travail de l'ordinateur est de trouver le meilleur mouvement pour une situation donnée, alors examinons ce processus en résumé pseudo-code:
for(var i=0; i<selectors.length; i++) <br> { <br> if(base2.DOM.Document.matchAll <br> (contentdoc, selectors[i]).length > 0) <br> { <br> used ++; <br> } <br> else <br> { <br> unused ++; <br> } <br> }
Nous évaluons une tactique, et si cela nous donne une bonne décision, nous avons terminé; Sinon, nous évaluons une autre tactique, etc., jusqu'à ce que nous ayons un mouvement, ou concluons qu'il n'y en a pas et que nous devons passer.
Chacune de ces fonctions tactiques exécute un processus coûteux, car il doit évaluer chaque position sur le tableau, ainsi que des positions futures potentielles, peut-être plusieurs fois chacune à la lumière de divers facteurs. L'exemple n'a que trois tactiques, mais dans le vrai jeu, il y a des dizaines de possibilités différentes, chacune coûteuse à évaluer.
L'une de ces évaluations individuellement est bien, mais toutes ensemble, s'exécutent consécutivement, font un processus trop intense qui gèle le navigateur.
Donc, ce que j'ai fait a été de diviser le code principal en tâches discrètes , chacune étant sélectionnée avec une instruction Switch, et a itéré à l'aide d'un minuteur asynchrone. La logique de cela n'est pas à un million de kilomètres de celles qui choisissent vos propres livres d'aventure que j'avais en tant qu'enfant, où chaque tâche se termine par un choix de tâches supplémentaires, le tout en temps réel, jusqu'à ce que nous atteignions la fin:
function process() <br> { <br> var above = 0, below = 0; <br> for(var i=0; i<200000; i++) <br> { <br> if(Math.random() * 2 > 1) <br> { <br> above ++; <br> } <br> else <br> { <br> below ++; <br> } <br> } <br> } <br> <br> <br> function test1() <br> { <br> var result1 = document.getElementById('result1'); <br> <br> var start = new Date().getTime(); <br> <br> for(var i=0; i<200; i++) <br> { <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [i=' + i + ']'; <br> <br> process(); <br> } <br> <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [done]'; <br> }
Ce code est beaucoup plus verbeux que l'original, donc si la taille de la taille du code était le seul impératif, ce ne serait clairement pas la voie à suivre.
Mais ce que nous essayons de faire ici, c'est créer un environnement d'exécution sans plafond, c'est-à-dire un processus qui n'a pas de limite supérieure en termes de complexité et de longueur; Et c'est ce que nous avons fait.
Ce modèle peut être étendu indéfiniment , avec des centaines ou même des milliers de tâches. Cela peut prendre beaucoup de temps pour courir, mais l'exécuter, et tant que chaque tâche individuelle n'est pas trop intense, elle fonctionnera sans tuer le navigateur.
La force de cette approche est également sa principale faiblesse: puisque la fonction intérieure est asynchrone, nous ne pouvons pas renvoyer une valeur de la fonction extérieure. Ainsi, par exemple, nous ne pouvons pas le faire (ou plutôt, nous le pouvons, mais il n'y aurait aucun point):
for(var i=0; i<selectors.length; i++) <br> { <br> if(base2.DOM.Document.matchAll <br> (contentdoc, selectors[i]).length > 0) <br> { <br> used ++; <br> } <br> else <br> { <br> unused ++; <br> } <br> }
que la fonction Checksomething () sera toujours renvoyer false parce que la fonction interne est asynchrone. La fonction extérieure reviendra avant que la première itération de la fonction intérieure ne se produise!
Cet exemple suivant est également inutile:
function process() <br> { <br> var above = 0, below = 0; <br> for(var i=0; i<200000; i++) <br> { <br> if(Math.random() * 2 > 1) <br> { <br> above ++; <br> } <br> else <br> { <br> below ++; <br> } <br> } <br> } <br> <br> <br> function test1() <br> { <br> var result1 = document.getElementById('result1'); <br> <br> var start = new Date().getTime(); <br> <br> for(var i=0; i<200; i++) <br> { <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [i=' + i + ']'; <br> <br> process(); <br> } <br> <br> result1.value = 'time=' + <br> (new Date().getTime() - start) + ' [done]'; <br> }
Nous sommes hors de portée de la fonction extérieure, donc nous ne pouvons pas en revenir; Cette valeur de retour disparaît inutilement dans l'éther.
Ce que nous peut faire ici, c'est de retirer une feuille des techniques de codage de l'Ajax et d'utiliser une fonction de rappel (qui dans cet exemple j'appelle "onComplete"):
<form action=""> <br> <fieldset> <br> <input type="button" value="test1" onclick="test1()" /> <br> <input type="text" /> <br> </fieldset> <br> </form> <br>
Ainsi, lorsque nous appelons Checksomething (), nous passons une fonction anonyme comme son argument, et cette fonction est appelée avec la valeur finale lorsque le travail est terminé:
function test2() <br> { <br> var result2 = document.getElementById('result2'); <br> <br> var start = new Date().getTime(); <br> <br> var i = 0, limit = 200, busy = false; <br> var processor = setInterval(function() <br> { <br> if(!busy) <br> { <br> busy = true; <br> <br> result2.value = 'time=' + <br> (new Date().getTime() - start) + ' [i=' + i + ']'; <br> <br> process(); <br> <br> if(++i == limit) <br> { <br> clearInterval(processor); <br> <br> result2.value = 'time=' + <br> (new Date().getTime() - start) + ' [done]'; <br> } <br> <br> busy = false; <br> } <br> <br> }, 100); <br> <br> }
élégant? Non. Mais fonctionnellement fonctionnel? Oui. Et c'est le point. En utilisant cette technique, nous pouvons écrire des scripts qui seraient autrement impossibles.
Avec cette technique dans notre kit, nous avons maintenant un moyen de lutter contre les projets JavaScript qui étaient auparavant en train de sortir du domaine de la possibilité. Le jeu pour lequel j'ai développé ce schéma a une logique assez simple, et donc un cerveau Brain assez simple, mais c'était encore trop pour l'itération conventionnelle; Et il y a beaucoup d'autres jeux qui nécessitent beaucoup plus d'influence!
Mon prochain plan est d'utiliser cette technique pour implémenter un moteur d'échecs JavaScript. Les échecs ont une vaste gamme de scénarios et de tactiques possibles, conduisant à des décisions qui pourraient prendre beaucoup de temps à calculer, beaucoup plus longtemps que ce qui aurait été possible sans cette technique. Un calcul intense est nécessaire pour créer même la machine de pensée la plus élémentaire, et j'avoue être très excité par les possibilités.
Si nous pouvons réaliser des astuces comme celle-ci, qui peut dire ce qui est possible? Traitement du langage naturel, heuristique… Peut-être que nous avons les éléments constitutifs pour développer l'intelligence artificielle en JavaScript!
Si vous avez aimé lire ce post, vous aimerez l'apprentissage; L'endroit pour acquérir de nouvelles compétences et techniques de la maîtrise. Les membres ont un accès instantané à tous les livres électroniques de SitePoint et aux cours en ligne interactifs, comme la programmation JavaScript pour le Web. Les commentaires sur cet article sont fermés. Vous avez une question sur JavaScript? Pourquoi ne pas le demander sur nos forums? Crédit d'image: Randen L PetersonLes travailleurs Web jouent un rôle crucial dans le multithreading JavaScript. Ils sont un moyen simple pour le contenu Web d'exécuter des scripts dans des threads d'arrière-plan. Le thread de travailleur peut effectuer des tâches sans interférer avec l'interface utilisateur. De plus, ils peuvent effectuer des E / S à l'aide de XMLHTTPRequest (bien que les attributs ResponseXML et Channel soient toujours nuls). Une fois créé, un travailleur peut envoyer des messages au code JavaScript qui l'a créé en publiant des messages à un gestionnaire d'événements spécifié par ce code (et vice versa).
JavaScript est intrinsèquement unique, mais il peut gérer le multithreading grâce à l'utilisation de rappels et de promesses asynchrones. Cela signifie que même si JavaScript fonctionne lui-même sur un seul thread, il peut planifier des tâches à exécuter à l'avenir, ce qui lui permet effectivement d'effectuer plusieurs tâches simultanément. Ceci est particulièrement utile pour gérer les opérations comme les demandes d'entrée de l'utilisateur ou d'API, qui peuvent être traitées en arrière-plan tandis que le thread principal continue d'exécuter un autre code.
Bien que le multithreading en JavaScript puisse être réalisé par le biais de travailleurs Web, il est important de noter que ces travailleurs n'ont pas accès au DOM ou à d'autres API Web. Ils sont limités à quelques types de données qu'ils peuvent envoyer dans les deux sens au fil principal. De plus, chaque travailleur est une instance distincte, donc il ne partage pas la portée ni aucune variable globale.
node.js a un dans le module appelé «cluster» qui vous permet de créer des processus enfants (travailleurs), qui partagent des ports de serveur avec le processus de nœud principal (maître). Ces processus d'enfants peuvent s'exécuter simultanément et fonctionner sur différentes tâches, implémentant efficacement le multithreading.
JavaScript a-t-il été conçu pour être unique pour éviter la complexité et le potentiel Problèmes avec la manipulation des données. Le multithreading peut entraîner des problèmes tels que les conditions de course, où la sortie dépend de la séquence ou du moment d'autres événements incontrôlables. On pensait qu'il serait plus facile d'optimiser un environnement unique, car il ne serait pas nécessaire de faire face aux dépendances entre les threads.
Pour utiliser des travailleurs Web pour la lecture multithre dans JavaScript, vous devez créer un nouvel objet de travailleur et spécifier un fichier JavaScript à exécuter dans le thread de travailleur. Vous pouvez ensuite communiquer avec le thread de travailleur à l'aide de la méthode PostMessage et en recevoir des messages à l'aide du gestionnaire d'événements OnMessage.
Quelle est la différence entre le multithreading et l'asynchrone. Programmation en javascrip tâches en même temps. Cependant, ils le font de différentes manières. Le multithreading implique plusieurs threads d'exécution, chaque thread effectuant une tâche différente. La programmation asynchrone, en revanche, implique un seul thread d'exécution, mais les tâches peuvent être démarrées puis mises en attente pour être terminées plus tard, permettant à d'autres tâches d'être effectuées entre-temps.
Le partage de données entre les threads en JavaScript peut être réalisé en utilisant SharedArrayBuffer et Atomics. SharedArrayBuffer permet de partager la mémoire entre le thread principal et les threads de travail, tandis que Atomics fournit des méthodes pour effectuer des opérations atomiques sûres sur la mémoire partagée.
Oui , vous pouvez utiliser le multithreading en JavaScript pour le développement frontal. Cependant, il est important de noter que les travailleurs du Web, qui permettent le multithreading, n'ont pas accès au DOM ou à d'autres API Web. Par conséquent, ils sont généralement utilisés pour des tâches qui n'impliquent pas de manipulation du DOM ou d'interaction avec la page Web, telles que la réalisation de calculs ou la gestion des données.
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!