Maison > Article > interface Web > Explication détaillée de la programmation événementielle dans Node.js_node.js
Dans le modèle de programmation traditionnel, les opérations d'E/S sont comme un appel de fonction locale ordinaire : le programme est bloqué avant que la fonction ne soit exécutée et ne peut pas continuer à s'exécuter. Le blocage des E/S est issu du modèle de tranche de temps précédent. Dans ce modèle, chaque processus est comme une personne indépendante, le but est de distinguer chaque personne, et chaque personne ne peut généralement faire qu'une seule chose à la fois et doit attendre seulement. lorsque vous avez terminé la chose précédente, pouvez-vous décider quoi faire ensuite. Mais ce modèle « un utilisateur, un processus », largement utilisé dans les réseaux informatiques et sur Internet, est peu évolutif. Lors de la gestion de plusieurs processus, beaucoup de mémoire sera consommée et le changement de contexte occupera également beaucoup de ressources. Cela représente une charge énorme pour le système d'exploitation et, à mesure que le nombre de processus augmente, les performances du système diminueront fortement.
Le multithreading est une alternative. Un thread est un processus léger qui partage la mémoire avec d'autres threads dans le même processus. Il ressemble plus à une extension du modèle traditionnel et est utilisé pour exécuter plusieurs threads simultanément. En attendant une opération d'E/S, d'autres threads peuvent prendre le relais. Lorsque l'opération d'E/S est terminée, le thread précédemment en attente sera réveillé. Autrement dit, un thread en cours d'exécution peut être interrompu puis repris plus tard. De plus, sous certains systèmes, les threads peuvent s'exécuter en parallèle sur différents cœurs d'un processeur multicœur.
Les programmeurs ne savent pas quand le thread s'exécutera à un moment précis. Ils doivent faire très attention à gérer les accès simultanés à la mémoire partagée, par exemple, ils doivent utiliser certaines primitives de synchronisation pour synchroniser l'accès à une certaine structure de données. des verrous ou des sémaphores. Utilisez-le pour forcer les threads à s'exécuter selon des comportements et des plans spécifiques. Les applications qui s'appuient fortement sur l'état partagé entre les threads sont sujettes à des problèmes étranges, hautement aléatoires et difficiles à trouver.
Une autre façon consiste à utiliser la collaboration multi-thread. Vous êtes responsable de libérer explicitement le CPU et de donner du temps CPU aux autres threads. Parce que vous contrôlez personnellement le plan d'exécution du thread, vous réduisez le besoin de synchronisation. mais cela augmente également la complexité du programme et les risques d'erreurs, et n'évite pas les problèmes de multi-threading.
Qu'est-ce que la programmation événementielle
La programmation basée sur les événements (programmation basée sur Evnet) est un style de programmation dans lequel les événements déterminent le flux d'exécution du programme. Les événements sont traités par des gestionnaires d'événements ou des rappels d'événements sont la fonction A actuelle qui est appelée lorsqu'un. un événement spécifique se produit, tel que la base de données renvoyant des résultats de requête ou l'utilisateur cliquant sur un bouton.
Rappelez-vous que dans le modèle traditionnel de programmation d'E/S bloquantes, une requête de base de données pourrait ressembler à ceci :
do_something_with(result);
Dans un modèle événementiel, cette requête ressemblerait à ceci :
do_something_with(result);
}
query('SELECT * FROM posts WHERE id = 1', query_finished);
Vous définissez d'abord une fonction appelée query_finished, qui contient ce qu'il faut faire une fois la requête terminée. Passez ensuite cette fonction en paramètre à la fonction de requête Lorsque la requête est exécutée, query_finished sera appelé au lieu de simplement renvoyer les résultats de la requête.
Lorsque l'événement qui vous intéresse se produit, la fonction que vous définissez sera appelée au lieu de simplement renvoyer la valeur du résultat. Ce modèle de programmation est appelé programmation événementielle ou programmation asynchrone. C'est l'une des caractéristiques les plus évidentes de Node. Ce modèle de programmation signifie que le processus en cours ne sera pas bloqué lors de l'exécution des opérations d'E/S. Par conséquent, plusieurs opérations d'E/S peuvent être exécutées en parallèle. la fonction de rappel correspondante sera appelée.
La couche inférieure de la programmation événementielle repose sur la boucle d'événements. La boucle d'événements est essentiellement une structure qui appelle en permanence deux fonctions : la détection d'événements et le déclenchement du processeur d'événements. Dans chaque boucle, le mécanisme de boucle d'événements doit détecter quels événements se sont produits. Lorsque l'événement se produit, il trouve la fonction de rappel correspondante et l'appelle.
La boucle d'événements n'est qu'un thread exécuté au sein du processus. Lorsqu'un événement se produit, le gestionnaire d'événements peut s'exécuter seul et ne sera pas interrompu, c'est-à-dire :
1. Au plus une fonction de rappel d'événement peut être exécutée à un moment précis
2. Aucun gestionnaire d'événements ne sera interrompu lors de l'exécution
Grâce à cela, les développeurs ne peuvent plus se soucier de la synchronisation des threads et de la modification simultanée de la mémoire partagée.
Un secret bien connu :
Les membres de la communauté de programmation système savent depuis longtemps que la programmation événementielle est le meilleur moyen de créer des services à haute concurrence, car elle n'a pas besoin de sauvegarder beaucoup de contexte, elle économise donc beaucoup de mémoire. , il n'y a pas beaucoup de changements de contexte et cela permet de gagner beaucoup de temps d'exécution.
Lentement, ce concept a pénétré dans d'autres plates-formes et communautés, et certaines implémentations de boucles d'événements célèbres ont émergé, telles que la machine Event de Ruby, AnyEvnet de Perl et Twisted de Python. En plus de celles-ci, il existe de nombreuses autres implémentations et langages.
Pour développer avec ces frameworks, vous devez acquérir des connaissances spécifiques liées au framework et aux bibliothèques de classes spécifiques au framework. Par exemple, lorsque vous utilisez Event Machine, afin de profiter des avantages du non-blocage, vous devez éviter d'utiliser. bibliothèques de classes de synchronisation et ne peut utiliser que la bibliothèque de classes asynchrones pour Event Machine. Si vous utilisez des bibliothèques de blocage (telles que la plupart des bibliothèques standard de Ruby), votre serveur perdra une évolutivité optimale car la boucle d'événements sera toujours constamment bloquée, empêchant parfois le traitement des événements d'E/S.
Node a été conçu à l'origine comme une plate-forme de serveur d'E/S non bloquante, donc en général, vous devez vous attendre à ce que tout le code exécuté dessus soit non bloquant. Étant donné que JavaScript est très petit et qu'il n'applique aucun modèle d'E/S (car il ne dispose pas de bibliothèque d'E/S standard), Node est construit dans un environnement très pur, sans aucun problème d'héritage.
Comment Node et JavaScript simplifient les applications asynchrones
Ryan Dahl, l'auteur de Node, a initialement utilisé C pour développer ce projet, mais a trouvé que la maintenance du contexte des appels de fonction était trop compliquée, ce qui entraînait une complexité de code élevée. Ensuite, il est passé à Lua, mais Lua dispose déjà de plusieurs bibliothèques d'E/S bloquantes. Mélanger le blocage et le non-blocage peut dérouter les développeurs et empêcher de nombreuses personnes de créer des applications évolutives, donc Lua a également été abandonné par Dahl. Enfin, il s'est tourné vers JavaScript. Les fermetures et les fonctions d'objet de premier niveau en JavaScript ont rendu JavaScript très adapté à la programmation événementielle. La magie de JavaScript est l’une des principales raisons pour lesquelles Node est si populaire.
Qu'est-ce qu'une fermeture
Une fermeture peut être comprise comme une fonction spéciale, mais elle peut hériter et accéder aux variables dans le cadre dans lequel elle est définie. Lorsque vous transmettez une fonction de rappel en tant que paramètre à une autre fonction, elle sera appelée plus tard. La magie est que lorsque la fonction de rappel est appelée plus tard, elle se souvient du contexte dans lequel elle est définie et des variables de contexte parent qu'elle contient. et ils sont accessibles normalement. Cette fonctionnalité puissante est au cœur du succès de Node.
L'exemple suivant montre comment les fermetures JavaScript fonctionnent dans un navigateur Web. Si vous souhaitez écouter l'événement autonome d'un bouton, vous pouvez faire ceci :
document.getElementById('myButton').onclick = function() {
clicCount = 1;
alert("cliqué sur " clickCount " fois.");
};
Voici comment cela fonctionne lorsque vous utilisez jQuery :
$('bouton#monbouton').click(function() {
clickedCount ;
alert('Cliqué sur ' clickCount ' fois.');
});
En JavaScript, les fonctions sont des objets de première classe, ce qui signifie que vous pouvez transmettre des fonctions en tant que paramètres à d'autres fonctions. Dans les deux exemples ci-dessus, le premier attribue une fonction à une autre fonction et le second transmet la fonction en tant que paramètre à une autre fonction. La fonction de gestionnaire (fonction de rappel) de l'événement click peut accéder à chaque variable sous le bloc de code où se trouve la fonction. est défini, dans ce cas, il a accès à la variable clickCount définie dans sa fermeture parent.
La variable clickCount se trouve dans la portée globale (la portée la plus externe en JavaScript). Elle enregistre le nombre de fois où l'utilisateur clique sur le bouton. C'est généralement une mauvaise habitude de stocker des variables dans la portée globale car cela peut facilement entrer en conflit avec. autre code. , vous devez placer les variables dans la portée locale où elles sont utilisées. La plupart du temps, simplement envelopper le code dans une fonction équivaut à créer une fermeture supplémentaire, afin d'éviter facilement de polluer l'environnement global, comme ceci :
var clickCount = 0;
$('bouton#monbouton').click(function() {
clicCount ;
alert('J'ai cliqué sur ' clickCount ' fois.');
} );
}());
Remarque : La septième ligne du code ci-dessus définit une fonction et l'appelle immédiatement. Il s'agit d'un modèle de conception courant en JavaScript : créer une nouvelle portée en créant une fonction.
Comment les fermetures aident la programmation asynchrone
Dans le modèle de programmation événementielle, écrivez d'abord le code qui sera exécuté après l'événement, puis placez le code dans une fonction, et enfin transmettez la fonction en tant que paramètre à l'appelant, qui sera ensuite appelé par la fonction appelant.En JavaScript, une fonction n'est pas une définition isolée. Elle mémorisera également le contexte de la portée dans laquelle elle est déclarée. Ce mécanisme permet aux fonctions JavaScript d'accéder au contexte où la fonction est définie et au contexte parent de toutes les variables. .
Lorsque vous transmettez une fonction de rappel en paramètre à l'appelant, cette fonction sera appelée ultérieurement. Même si la portée dans laquelle la fonction de rappel est définie est terminée, lorsque la fonction de rappel est appelée, elle peut toujours accéder à toutes les variables de la portée terminée et de sa portée parent. Comme dans le dernier exemple, la fonction de rappel est appelée dans click() de jQuery, mais elle peut toujours accéder à la variable clickCount.
La magie des fermetures a été montrée plus tôt. Passer des variables d'état à une fonction vous permet d'effectuer une programmation événementielle sans maintenir l'état. Le mécanisme de fermeture de JavaScript vous aidera à les maintenir.
Résumé
La programmation événementielle est un modèle de programmation qui détermine le flux d'exécution du programme via le déclenchement d'événements. Les programmeurs enregistrent des fonctions de rappel (souvent appelées gestionnaires d'événements) pour les événements qui les intéressent, et le système appelle ensuite le gestionnaire d'événements enregistré lorsque l'événement se produit. Ce modèle de programmation présente de nombreux avantages que le modèle de programmation par blocage traditionnel n'a pas. Dans le passé, pour obtenir des fonctionnalités similaires, il fallait utiliser le multi-processus/multi-threading.JavaScript est un langage puissant car sa fonction et ses propriétés de fermeture des objets de premier type le rendent bien adapté à la programmation événementielle.