La popularité massive du terme "asynchrone" était dans la vague de Web 2.0, qui a balayé le Web avec JavaScript et Ajax. Mais l'asynchrone est rare dans la plupart des langages de programmation de haut niveau. PHP reflète le mieux cette fonctionnalité: il bloque non seulement de manière asynchrone, mais ne fournit pas non plus plusieurs threads. PHP est exécuté de manière bloquante synchrone. Ces avantages sont bénéfiques pour les programmeurs d'écrire une logique commerciale en séquence, mais dans des applications de réseau complexes, le blocage le fait échouer à être plus concurrent.
Du côté du serveur, les E / S sont très chères et les E / S distribuées sont plus chères. Ce n'est que lorsque le backend peut répondre rapidement aux ressources que l'expérience frontale peut devenir meilleure. Node.js est la première plate-forme à utiliser Asynchrone comme méthode de programmation principale et concept de conception. Accompagnés d'E / S asynchrones, motivées par des événements et unique, ils forment le ton du nœud. Cet article présentera comment Node implémente les E / S asynchrones.
1. Concepts de base
"Async" et "non bloquant" sonnent la même chose, et en termes de résultats réels, tous deux atteignent le but du parallélisme. Mais du point de vue des E / S du noyau informatique, il n'y a que deux façons: le blocage et le non-blocage. Donc, asynchrone / synchrone et blocage / non bloquant, sont en fait deux choses différentes.
1.1 Blocking E / S et E / S non bloquants
Une caractéristique du blocage des E / S est qu'après l'appel, vous devez attendre que toutes les opérations soient terminées au niveau du noyau système avant la fin de l'appel. En prenant la lecture d'un fichier sur le disque à titre d'exemple, cet appel se termine une fois que le noyau système a terminé la recherche de disque, lit les données et copie les données en mémoire.
Le blocage des E / S fait attendre le processeur des E / S, la perte de temps d'attente et la puissance de traitement du CPU ne peut pas être pleinement utilisée. La caractéristique des E / S non bloquantes est qu'elle reviendra immédiatement après l'appel, et la tranche de temps CPU peut être utilisée pour gérer d'autres transactions après le retour. Étant donné que l'E / S complète n'est pas terminée, ce qui est immédiatement renvoyé n'est pas les données attendues par la couche commerciale, mais le statut de l'appel actuel. Afin d'obtenir les données complètes, l'application doit appeler à plusieurs reprises l'opération d'E / S pour confirmer si elle est terminée (c'est-à-dire le sondage). Les techniques de sondage nécessitent ce qui suit:
1. Lire: Vérification de l'état d'E / S par des appels répétés est la méthode de performance la plus originale et la plus basse
2.Sélectionnez: améliorations à lire, juger l'état de l'événement sur le descripteur de fichier. L'inconvénient est que le nombre maximum de descripteurs de fichiers est limité.
3.Poll: améliorations à sélectionner, en utilisant des listes liées pour éviter une limite de nombre maximale, mais lorsqu'il existe de nombreux descripteurs, les performances sont encore très faibles
4.Pepoll: Si aucun événement d'E / S n'est vérifié lors du sondage, il dormira jusqu'à ce que l'événement se produise et le réveillera. Il s'agit du mécanisme de notification des événements d'E / S le plus efficace sous Linux.
Le sondage répond à la nécessité d'E / S non bloquantes pour assurer une acquisition complète de données, mais pour les applications, elle peut toujours compter comme une sorte de synchronisation car elle doit encore attendre que les E / S reviennent complètement. Pendant l'attente, le CPU est utilisé pour traverser l'état du descripteur de fichier ou pour hiberner en attendant que les événements se produisent.
1.2 E / S asynchrone dans l'idéal et la réalité
Des E / S asynchrones parfaites devraient être l'application qui initie un appel non bloquant et peut gérer directement la tâche suivante sans interrogation, il suffit de passer les données à l'application via un signal ou un rappel une fois les E / S terminées.
En réalité, les E / S asynchrones ont des implémentations différentes sous différents systèmes d'exploitation. Par exemple, * Nix Platform adopte un pool de thread personnalisé, tandis que Windows Platform adopte un modèle IOCP. Node fournit Libuv en tant que couche d'encapsulation abstraite pour encapsuler les jugements de compatibilité des plates-formes et garantit que la mise en œuvre d'E / S asynchrones du nœud supérieur et des plates-formes inférieures est indépendante. Il convient de souligner que nous mentionnons souvent que le nœud est unique, ce qui signifie seulement que l'exécution JavaScript est dans un seul thread, et il existe d'autres pools de threads qui effectuent réellement des tâches d'E / S dans le nœud.
2. E / S asynchrone de Node
2.1 Boucle d'événement
Le modèle d'exécution de Node est en fait une boucle d'événement. Lorsque le processus démarre, le nœud crée une boucle infinie et chaque processus d'exécution du corps de boucle devient une tique. Chaque processus de tick consiste à vérifier s'il y a des événements qui attendent d'être traités. Si c'est le cas, les événements et leurs fonctions de rappel connexes seront supprimés. S'il y a des fonctions de rappel associées, elles seront exécutées, puis la boucle suivante sera entrée. S'il n'y a plus de traitement d'événements, quittez le processus.
2.2 Observer
Il y a plusieurs observateurs dans chaque boucle d'événements, et en demandant à ces observateurs, nous pouvons déterminer s'il y a des événements à traiter. La boucle d'événements est un modèle de producteur / consommateur typique. Dans le nœud, les événements proviennent principalement des demandes de réseau, de fichiers d'E / S, etc. Ces événements ont des observateurs d'E / S de réseau correspondants, des observateurs d'E / S de fichiers, etc. La boucle d'événement sort l'événement de l'observateur et le traite.
2.3 Demander un objet
Pendant la transition de JavaScript au noyau effectuant des opérations d'E / S, il existe un produit intermédiaire appelé l'objet de demande. En prenant la méthode la plus simple de fs.open () dans Windows (ouvrez un fichier et obtenez un descripteur de fichier en fonction du chemin et des paramètres spécifiés) par exemple, des appels JS aux modules intégrés, les appels système via Libuv sont en fait appelés la méthode UV_FS_OPEN (). Pendant le processus d'appel, un objet de demande FSREQWRAP est créé et les paramètres et méthodes passés de la couche JS sont encapsulés dans cet objet de demande. La fonction de rappel qui nous inquiète le plus est définie sur la propriété onCompte_Sym de cet objet. Une fois l'objet enveloppé, poussez l'objet FSREQWRAP dans le pool de thread et attendez l'exécution.
À ce stade, l'appel JS revient immédiatement et le fil JS peut continuer à effectuer des opérations ultérieures. L'opération d'E / S actuelle attend l'exécution dans le pool de threads, qui complète la première étape de l'appel asynchrone.
2.4 Exécuter des rappels
La notification de rappel est la deuxième phase des E / S asynchrones. Une fois l'opération d'E / S dans le pool de threads appelé, les résultats obtenus seront stockés, puis IOCP est informé que l'opération actuelle de l'objet a été terminée et que le thread renvoie le pool de threads. Lors de chaque exécution de tick, l'observateur d'E / S de la boucle d'événement appellera la méthode pertinente pour vérifier s'il existe des demandes remplies dans le pool de threads. S'il existe, l'objet de demande sera ajouté à la file d'attente de l'observateur d'E / S, puis traité comme un événement.
3. API non i / o asynchrone
Il existe également des API asynchrones qui ne sont pas liées aux E / S dans le nœud, telles que les minuteries setTimeout (), SetInterval (), Process.NextTick () et SetImmDate () qui exécutent immédiatement des tâches de manière asynchrone, etc., qui seront brièvement introduites ici.
3.1 API de minuterie
Les API du côté du navigateur de setTimeout () et setInterval () sont cohérentes. Leur principe de mise en œuvre est similaire aux E / S asynchrones, mais ils ne nécessitent pas la participation du pool de threads d'E / S. La minuterie créée en appelant l'API de la minuterie sera insérée dans un arbre rouge et noir à l'intérieur de l'observateur de la minuterie. Le tick de chaque boucle d'événements ira itérara l'objet de minuterie de l'arbre rouge et noir pour vérifier si le temps a dépassé. S'il dépasse, un événement sera formé et la fonction de rappel sera exécutée immédiatement. Le principal problème avec une minuterie est que son temps de synchronisation n'est pas particulièrement précis (millisecondes, dans la tolérance).
3.2 API d'exécution des tâches asynchrones
Avant que le nœud n'apparaît, de nombreuses personnes pourraient appeler cela afin d'effectuer immédiatement une tâche de manière asynchrone:
La copie de code est la suivante:
setTimeout (function () {
// FAIRE
}, 0);
En raison des caractéristiques des boucles d'événements, la minuterie n'est pas suffisamment précise et l'utilisation d'un arbre rouge et noir nécessite l'utilisation d'une minuterie et la complexité de divers temps de fonctionnement est O (log (n)). La méthode process.nextTick () ne mettra que la fonction de rappel dans la file d'attente et la retirera et l'exécute dans la prochaine série de tiques. La complexité est O (1) et elle est plus efficace.
De plus, il existe une méthode SetImMediate () similaire à la méthode ci-dessus, tous deux retardant l'exécution de la fonction de rappel. Cependant, le premier a une priorité plus élevée que le second, car la boucle d'événement vérifie l'observateur en séquence. De plus, l'ancienne fonction de rappel est enregistrée dans un tableau, et chaque cycle de tick exécutera toutes les fonctions de rappel dans le tableau; Ce dernier résultat est enregistré dans une liste liée, et chaque cycle de tick exécutera une seule fonction de rappel.
4. Serveurs d'événements et hautes performances
L'exemple précédent illustre comment le nœud implémente les E / S asynchrones. En fait, Node applique également les E / S asynchrones pour le traitement des socket réseau, qui est également la base du nœud pour créer un serveur Web. Les modèles de serveurs classiques sont:
1. Synchrones: une seule demande peut être traitée à la fois, et le reste des demandes est dans un état d'attente
2. Par processus / par demande: démarrez un processus pour chaque demande, mais les ressources système sont limitées et n'ont pas d'évolutivité.
3. Par thread / par demande: Démarrez un thread pour chaque demande. Les threads sont plus légers que les processus, mais chaque thread occupe une certaine quantité de mémoire. Lorsque de grandes demandes simultanées arrivent, la mémoire s'épuisera bientôt.
Le célèbre Apache adopte la forme par thread / par réflexion, c'est pourquoi il est difficile de faire face à une concurrence élevée. Le nœud gère les demandes via des méthodes motivées par des événements, ce qui peut sauver les frais généraux de création et de destruction de threads. Dans le même temps, le système d'exploitation a moins de threads lors de la planification des tâches, et le coût de la commutation de contexte est également très faible. Le nœud peut gérer les demandes de manière ordonnée même avec un grand nombre de connexions.
Le serveur bien connu Nginx abandonne également la méthode multi-threading et adopte la même méthode axée sur les événements que le nœud. Maintenant, Nginx est en grande partie pour remplacer Apache. Nginx est écrit en C pur et a des performances élevées, mais il convient uniquement aux serveurs Web, utilisés pour la proxyation inverse ou l'équilibrage de charge, etc. Le nœud peut créer les mêmes fonctions que Nginx, et peut également gérer diverses entreprises spécifiques, et ses propres performances sont également bonnes. Dans les projets réels, nous pouvons les combiner pour obtenir les meilleures performances de l'application.