Dans les modules de programmation traditionnels, les opérations d'E / S sont comme un appel de fonction local ordinaire: le programme est bloqué avant l'exécution de la fonction et ne peut pas continuer à s'exécuter. Les E / S bloquées proviennent du modèle de tranche de temps précédent, où chaque processus est comme une personne indépendante, dans le but de distinguer tout le monde, et tout le monde ne peut généralement faire qu'une seule chose en même temps, et doit attendre que la chose précédente soit fait avant de décider quoi faire ensuite. Cependant, ce modèle de "un utilisateur, un processus" qui est largement utilisé sur les réseaux informatiques et Internet est très évolutif. Lors de la gestion de plusieurs processus, il consomme beaucoup de mémoire et le changement de contexte occupera également beaucoup de ressources. Ce sont un énorme fardeau pour le système d'exploitation, et à mesure que le nombre de processus augmente, les performances du système se décomposeront fortement.
Le multithreading est une alternative. Un fil est un processus léger qui partage la mémoire avec d'autres threads dans le même processus. Cela ressemble plus à une extension du modèle traditionnel, qui est utilisé pour exécuter plusieurs threads simultanément. Lorsqu'un thread attend les opérations d'E / S, d'autres threads peuvent reprendre le CPU. Une fois l'opération d'E / S terminée, le fil en attente devant sera éveillé. C'est-à-dire qu'un fil en cours d'exécution peut être interrompu puis reprendre plus tard. De plus, les threads peuvent fonctionner en parallèle dans différents noyaux de processeurs multi-core sous certains systèmes.
Les programmeurs ne savent pas à quelle heure le thread fonctionnera. Ils doivent faire attention à gérer l'accès simultané à la mémoire partagée, ils doivent donc utiliser certaines primitives de synchronisation pour synchroniser l'accès à une certaine structure de données, comme l'utilisation de serrures ou de sémaphores, pour forcer les threads à exécuter dans des comportements et des plans spécifiques. Les applications qui reposent fortement sur l'état partagé entre les fils peuvent facilement avoir des problèmes étranges avec une forte aléatoire et des difficultés à trouver.
Une autre façon consiste à utiliser une collaboration multi-thread, où vous êtes responsable de la libération explicite du CPU et de la remise du temps du processeur à d'autres threads. Étant donné que vous contrôlez personnellement le plan d'exécution du thread, le besoin de synchronisation est réduit, mais il augmente également la complexité du programme et le risque d'erreurs, et n'évite pas les problèmes de multi-threading.
Qu'est-ce que la programmation axée sur l'événement
La programmation axée sur les événements est un style de programmation, où les événements déterminent le processus d'exécution d'un programme. Les événements sont gérés par des gestionnaires d'événements ou des rappels d'événements. Les rappels d'événements sont des fonctions appelées lorsqu'un événement spécifique se produit, comme la base de données renvoie le résultat de la requête ou que l'utilisateur clique sur un bouton.
Rappelons que dans le mode de programmation d'E / S bloqué traditionnel, les requêtes de base de données peuvent ressembler à ceci:
La copie de code est la suivante:
result = query ('select * dans les messages où id = 1');
do_something_with (résultat);
La fonction de requête ci-dessus conservera le thread ou le processus actuel dans un état d'attente jusqu'à ce que la base de données sous-jacente termine l'opération de requête et le retour.
Dans le modèle axé sur les événements, cette requête deviendra ceci:
La copie de code est la suivante:
query_finished = function (result) {
do_something_with (résultat);
}
query ('select * dans les messages où id = 1', query_finished);
Vous définissez d'abord une fonction appelée Query_Finished, qui contient ce que vous voulez faire une fois la requête terminée. Ensuite, passez cette fonction comme un paramètre à la fonction de requête. Query_Finished sera appelé après l'exécution de la requête, au lieu de simplement renvoyer le résultat de la requête.
Lorsqu'un é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 axée sur les événements ou programmation asynchrone. C'est l'une des caractéristiques les plus évidentes du nœud. Ce modèle de programmation signifie que le processus actuel 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 et la fonction de rappel correspondante sera appelée une fois l'opération terminée.
La couche sous-jacente de programmation axée sur les événements repose sur des boucles d'événements. Les boucles d'événements sont essentiellement une structure dans laquelle la détection d'événements et le processeur d'événements déclenchent l'appel de boucle continue de ces deux fonctions. Dans chaque boucle, le mécanisme de boucle d'événement doit détecter les événements qui se sont produits. Lorsque l'événement se produit, il trouve la fonction de rappel correspondante et l'appelle.
La boucle d'événements est juste un fil exécuté dans le processus. Lorsqu'un événement se produit, le processeur 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 fonctionne à un moment précis
2. Aucun processeur d'événements n'est interrompu lors de l'exécution
Avec cela, les développeurs ne peuvent plus avoir de maux de tête sur la synchronisation du thread et la modification simultanée de la mémoire partagée.
Un secret bien connu:
Il y a longtemps, les gens de la communauté de programmation système savaient que la programmation axée sur les événements était le meilleur moyen de créer des services de concurrence élevés car il n'avait pas à économiser beaucoup de contexte, donc il a économisé beaucoup de mémoire, pas beaucoup de changement de contexte, et a fait gagner beaucoup de temps d'exécution.
Lentement, ce concept a imprégné d'autres plates-formes et communautés, et certaines implémentations de boucle d'événements célèbres ont émergé, telles que Ruby's Event Machine, Perl's Anyevnet et Python's Twisted. En plus de cela, il existe de nombreuses autres implémentations et langues.
Pour développer ces cadres, vous devez apprendre des connaissances spécifiques liées au framework et aux bibliothèques de classe spécifiques au framework. Par exemple, lorsque vous utilisez une machine d'événements, afin de profiter des avantages du non-bloquant, vous devez éviter d'utiliser des bibliothèques de classe synchrones et ne peut utiliser que des bibliothèques de classe asynchrones de la machine d'événements. Si vous utilisez une bibliothèque de blocage (comme la plupart des bibliothèques standard de Ruby), votre serveur perd son évolutivité optimale car la boucle d'événement sera toujours bloquée constamment, bloquant le traitement des événements d'E / S de temps à autre.
Le nœud a été à l'origine conçu 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é soit non bloquant. Parce que JavaScript est très petit et qu'il ne force aucun modèle d'E / S (car il n'a pas de bibliothèque de classe d'E / S standard), le nœud est construit dans un environnement très pur et il n'y aura pas de problèmes hérités.
Comment le nœud et le javascript simplifient les applications asynchrones
L'auteur de Node, Ryan Dahl, a initialement utilisé C pour développer ce projet, mais a constaté que le contexte de maintien des appels de fonction était trop complexe, entraînant une complexité de code élevée. Puis il est passé à Lua, mais Lua a déjà plusieurs bibliothèques d'E / S de blocage. Le mélange du blocage et du non-bloquant peut confondre les développeurs et donc empêcher de nombreuses personnes de construire des applications évolutives. Par conséquent, Lua a également été abandonné par Dahl. Enfin, il s'est tourné vers JavaScript, les fermetures en JavaScript et les fonctions des objets de premier niveau, qui rendent JavaScript très adapté à la programmation axée sur les événements. La magie de JavaScript est l'une des principales raisons pour lesquelles le nœud 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 la portée qu'il est défini. Lorsque vous passez une fonction de rappel comme paramètre à une autre fonction, elle sera appelée plus tard. La magie est que lorsque cette fonction de rappel est appelée plus tard, elle se souvient en fait du contexte dans lequel il se définit et des variables dans le contexte parent, et peut également y accéder normalement. Cette fonctionnalité puissante est le cœur du succès de Node.
L'exemple suivant montrera comment les fermetures JavaScript fonctionnent dans un navigateur Web. Si vous souhaitez écouter un événement autonome sur un bouton, vous pouvez le faire:
La copie de code est la suivante:
var clickCount = 0;
document.getElementById ('mybutton'). onclick = function () {
ClickCount + = 1;
alert ("cliquer" + clickCount + "fois.");
};
C'est ainsi que l'utilisation de jQuery:
La copie de code est la suivante:
var clickCount = 0;
$ ('bouton # mybutton'). Cliquez sur (fonction () {
Cliquez surCount ++;
alert ('cliquer' + clickCount + 'fois.');
});
Dans JavaScript, les fonctions sont le premier type d'objets, ce qui signifie que vous pouvez transmettre les fonctions comme paramètres à d'autres fonctions. Dans les deux exemples ci-dessus, le premier attribue une fonction à une autre fonction, et le second passe la fonction comme un paramètre à une autre fonction. La fonction de traitement des événements de clic (fonction de rappel) peut accéder à chaque variable sous le bloc de code où la fonction la définit. Dans cet exemple, il peut accéder à la variable ClickCount définie dans sa fermeture parent.
La variable ClickCount est dans la portée globale (la portée la plus externe de JavaScript), ce qui enregistre le nombre de fois que l'utilisateur clique sur un bouton. C'est généralement une mauvaise habitude de stocker des variables sous la portée globale, car il est facile de confronter un autre code, et vous devez mettre des variables dans la portée locale où vous les utilisez. La plupart du temps, le simple fait d'envelopper le code avec une fonction équivaut à créer une autre fermeture, ce qui peut facilement éviter de polluer l'environnement mondial, comme ceci:
La copie de code est la suivante:
(fonction() {
var clickCount = 0;
$ ('bouton # mybutton'). Cliquez sur (fonction () {
ClickCount ++;
alert ('cliquer' + 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 commun dans JavaScript: créez une nouvelle portée en créant une fonction.
Comment les fermetures aident la programmation asynchrone
Dans le modèle de programmation motivé par des événements, écrivez d'abord le code à exécuter après l'événement, puis mettez le code dans une fonction, et enfin transmettez la fonction en tant que paramètre à l'appelant, puis appelez-le par la fonction de l'appelant plus tard.
Dans JavaScript, une fonction n'est pas une définition isolée. Il se souvient également du contexte de la portée qu'il est déclaré. Ce mécanisme permet aux fonctions JavaScript d'accéder au contexte dans lequel la définition de la fonction est située et toutes les variables dans le contexte parent.
Lorsque vous transmettez une fonction de rappel comme paramètre à l'appelant, la fonction sera appelée à un moment ultérieur. Même si la portée qui définit la fonction de rappel est terminée, lorsque la fonction de rappel est appelée, elle peut toujours accéder à toutes les variables de la portée finale et de sa portée parentale. Comme le dernier exemple, la fonction de rappel est appelée à l'intérieur du clic () de jQuery, mais il peut toujours accéder à la variable ClickCount.
La magie des fermetures est montrée plus tôt. Passer des variables d'état à une fonction vous permet d'effectuer une programmation axée sur les événements sans maintenir des états. Le mécanisme de fermeture de JavaScript vous aidera à les maintenir.
résumé
La programmation axée sur les événements est un modèle de programmation qui détermine le processus d'exécution du programme via le déclenchement des événements. Les programmeurs enregistrent les fonctions de rappel pour les événements qui les intéressent (généralement appelés gestionnaires d'événements), 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 les modèles de programmation de blocage traditionnels n'ont pas. Dans le passé, pour implémenter des fonctionnalités similaires, le multi-processus / multi-threading doit être utilisé.
JavaScript est un langage puissant en raison de son premier type de propriétés de fonction et de fermeture de l'objet, ce qui le rend très adapté à la programmation axée sur les événements.