Explorez la théorie derrière la boucle de traitement des événements dans Node.js
P粉564192131
P粉564192131 2024-04-01 19:35:24
0
1
499

Je regarde cet aperçu décrivant les algorithmes de traversée de fichiers en JavaScript

// ES6 version using asynchronous iterators, compatible with node v10.0+

const fs = require("fs");
const path = require("path");

async function* walk(dir) {
    for await (const d of await fs.promises.opendir(dir)) {
        const entry = path.join(dir, d.name);
        if (d.isDirectory()) yield* walk(entry);
        else if (d.isFile()) yield entry;
    }
}

// Then, use it with a simple async for loop
async function main() {
    for await (const p of walk('/tmp/'))
        console.log(p)
}

Je suis frappé par l'impulsion (du langage, pas de l'auteur) d'entasser async/wait dans chaque crevasse de l'algorithme. Je ne connais pas grand-chose à l'architecture de Node.js, mais je suppose qu'il y a une intention derrière la fonctionnalité fortement asynchrone ? C'est très déroutant et nouveau pour moi qui ai principalement été exposé au modèle de thread/processus procédural déclaratif C/C++. Ce que je veux savoir, ce n'est pas "quel est le modèle" ou "est-il meilleur", car les gens peuvent avoir des opinions, mais "quelle est l'idée qui conduit à l'asynchronicité ?" des problèmes d'efficacité ?

Ma question est "Pourquoi tant d'asynchronicité ?". Je ne cherche pas un avis, mais quelqu'un qui comprend l'histoire et l'évolution de Node.js ou JavaScript pour expliquer son architecture.

Points clés : https://gist.github.com/lovasoa/8691344

P粉564192131
P粉564192131

répondre à tous(1)
P粉328911308

Eh bien, à un niveau élevé, la première partie de cette phrase et la deuxième partie de cette phrase sont en conflit. Si le serveur est inefficace, il ne sera pas en mesure de répondre correctement à un ensemble de requêtes client arrivant en même temps. Par conséquent, vous souhaitez rendre votre serveur plus efficace afin qu’il puisse être aussi réactif que possible aux demandes des clients.


Maintenant, nous devons revenir en arrière de quelques étapes et comprendre ce qui se passe exactement dans le code que vous avez montré. Premièrement, même si le langage est Javascript, la logique de base de ce code et la manière dont il utilise asyncawait et les générateurs ne sont pas uniquement dues au langage Javascript. Cela est dû à l'environnement spécifique dans lequel Javascript s'exécute, en l'occurrence nodejs.

Cet environnement utilise une boucle d'événements et exécute un seul thread Javascript. D'autres threads du système d'exploitation sont utilisés pour diverses choses du système et certaines implémentations de bibliothèques, mais lorsque nodejs exécute votre Javascript, il n'exécute qu'un seul élément de Javascript à la fois (un seul thread).

En parallèle, lorsque vous concevez un serveur, vous souhaitez qu'il soit capable de répondre à un grand nombre de requêtes entrantes. Vous ne voulez pas qu'il doive traiter une demande et que toutes les autres demandes attendent la fin de la première demande avant de commencer la suivante. Cependant, le modèle de boucle d'événements nodejs n'utilise pas plusieurs threads et n'exécute donc pas directement plusieurs gestionnaires de requêtes en même temps.

La solution déployée par nodejs vient du fait que pour les différents gestionnaires de requêtes du serveur, l'activité principale et ce qui passe le plus de temps à gérer les requêtes sont les E/S (comme le réseau, les E/S de fichiers ou la base de données) /O). Il s'agit d'opérations de niveau inférieur qui ont leur propre implémentation (non Javascript).

Ainsi, il déploie un modèle asynchrone pour toutes les opérations d'E/S. Un serveur bien écrit peut lancer une opération d'E/S asynchrone, et pendant son traitement (pas dans le code exécuté dans l'interpréteur Nodejs lui-même), l'interpréteur et la boucle d'événements NodeJS sont libres de faire autre chose et de gérer une autre requête. Quelque temps plus tard, lorsque cette opération asynchrone est terminée, un événement est inséré dans la boucle d'événements et lorsque l'interpréteur termine l'opération qu'il effectuait, il peut traiter les résultats de l'opération asynchrone et poursuivre l'opération.

De cette façon, Javascript n'est exécuté que dans un seul thread, mais de nombreuses requêtes entrantes peuvent être "traitées" en même temps.

Oui, il s'agit d'un modèle complètement différent de l'ancien modèle de thread C/C++. Soit vous apprenez ce modèle différent pour écrire du code serveur efficace et efficient dans Nodejs, soit vous ne le faites pas. Si vous souhaitez vous en tenir à l'ancien modèle, choisissez un environnement différent qui exécute le gestionnaire de requêtes dans un thread (Java, C++, etc.) et essayez de bien le faire (avec la surcharge de conception et de test associée bien sûr) écrit correctement et minutieusement testé pour toutes les concurrences multithread).

L'un des avantages du modèle nodejs est qu'il est moins sensible à de nombreux problèmes de concurrence que rencontrent les modèles d'exécution multithread. Les modèles Nodejs présentent également certaines lacunes qui nécessitent parfois des solutions de contournement. Par exemple, si vous avez du code gourmand en CPU dans un gestionnaire de requêtes écrit en Javascript, cela ralentira toujours les choses et vous devrez trouver un moyen de déplacer le code gourmand en CPU hors de la boucle d'événements principale et vers un autre. thread , ou un processus (peut-être même une file d'attente de travail). Cependant, les E/S sont toutes asynchrones et peuvent rester sur le thread principal sans poser de problèmes.

Moins il y a de code qui doit être dans des threads simultanés séparés, moins vous êtes susceptible de rencontrer de bogues de concurrence et le code est plus facile à tester entièrement.

Eh bien, vous voulez une boucle parce que vous essayez de boucler quelque chose. Vous souhaitez utiliser des opérations asynchrones dans la boucle d'événements lorsque vous ne souhaitez pas bloquer la boucle d'événements ou lorsqu'il s'agit du seul type d'opération dont vous disposez pour effectuer une tâche (comme effectuer une recherche dans une base de données).

Utiliser des opérations asynchrones ne consiste pas à optimiser quelque chose. Il s'agit de la conception de base consistant à écrire un bon code serveur et à ne pas bloquer la boucle d'événements. Et, en fait, les interfaces possibles dans nodejs (telles que les interfaces de base de données ou les interfaces réseau) ne fournissent que des interfaces asynchrones.

La façon dont vous avez posé cette question suggère que vous gagneriez à mieux comprendre l'architecture de base de Nodejs et à en savoir plus sur le fonctionnement de la boucle d'événements et le fonctionnement des opérations d'E/S asynchrones.

Tout d'abord, si vous utilisez une API asynchrone (comme un réseau ou une base de données), vous n'avez pas le choix. Vous concevrez du code asynchrone pour utiliser cette API. Si vous avez le choix entre utiliser une API asynchrone ou synchrone (tout comme vous le faites avec l'accès au système de fichiers dans Node.js), vous pouvez alors choisir de bloquer ou non la boucle d'événements à chaque appel d'API. Si vous bloquez la boucle d'événements, vous nuirez gravement à l'évolutivité et à la réactivité de votre serveur.


Cet exemple de code particulier essaie vraiment d'utiliser l'évier de cuisine des fonctionnalités du langage asynchrone dans la même implémentation que asyncawait、生成器和 yield . En général, je ne fais pas ça. Tout l'intérêt de cette implémentation est de pouvoir créer une interface utilisable très simplement comme ceci :

for await (const p of walk('/tmp/')) {
    ...
}

Et walk() 的内部是异步的。此实现向 API 用户隐藏了几乎所有异步实现的复杂性,这使得 API 更易于编码。通过将单个 await placés au bon endroit, les utilisateurs de l'API peuvent coder de manière quasi synchrone. Le but de ces fonctionnalités du langage Javascript (promesses, async, wait, générateurs, etc.) est de faciliter le codage des opérations asynchrones.

Avantages du modèle de boucle d'événement

Facile à programmer. Vous n'avez généralement pas à faire face à des problèmes de concurrence lors de l'accès aux données partagées à partir de threads, car tout Javascript s'exécute dans le même thread, donc tous les accès aux données partagées proviennent du même thread. Vous n'avez pas besoin d'un mutex pour accéder aux données partagées. Vous ne courez aucun risque de blocage avec ces mutex.

Moins d'erreurs. L'accès aux données publiques à partir des threads est beaucoup plus difficile pour écrire du code sans bug. S'il n'est pas parfaitement écrit, le code peut être soumis à des conditions de concurrence ou manquer de protection contre la concurrence. De plus, ces conditions de concurrence sont souvent difficiles à tester et peuvent ne devenir apparentes que lorsque votre serveur est soumis à une forte charge, et même dans ce cas, elles ne sont pas faciles à reproduire.

Une évolutivité plus élevée (dans certains cas). Pour le code principalement lié aux E/S, un modèle de boucle d'événements coopératif peut conduire à une plus grande évolutivité. En effet, chaque requête en cours de traitement n'entraîne pas la création d'un thread de système d'exploitation distinct ni de sa surcharge supplémentaire. Au lieu de cela, il n’y a qu’une petite quantité d’état de l’application, généralement sous forme de fermetures liées à l’attente du prochain rappel ou de la prochaine promesse.

Articles sur la programmation de boucles d'événements

Pourquoi les enfants cools utilisent les boucles d'événements - Il s'agit ici de l'utilisation de boucles d'événements dans la programmation Java, mais la discussion s'applique à n'importe quel environnement

Exemples de fils de discussion et d'événements

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal