12
votes

Pourquoi une fonction et un rappel ne bloquant pas dans Node.js?

La compréhension novice du nœud est que si je réécrire un code synchrone ou en ligne, pour utiliser des fonctions / rappels, je peux vous assurer que mon code est non bloqué. Je suis curieux de savoir comment cela fonctionne en termes de pile d'événements. L'exemple simple d'ici: Ne comprend pas le rappel - Stackoverflow est que cela bloquera: xxx pré>

tandis que cela ne va pas: p>

callback = function(post){
doSomethingWithPost(post)
}

db.query("select * from posts where id = 1",callback);
doSomethingElse();


0 commentaires

4 Réponses :


19
votes

Imaginez que vous utilisez la caisse enregistreuse dans une boulangerie. Vous gérez vos clients séquentiellement et de manière synchrone, comme celui-ci:

  1. prenez la commande
  2. Dire Baker de cuire le pain
  3. Attendez que le pain soit cuit au four
  4. Charge de l'argent
  5. livrer du pain
  6. Goto 1 - Next Client

    qui sera très lent. Maintenant, essayez plutôt de prendre les commandes de manière séquentielle, mais gérez vos clients de manière asynchrone:

    1. prenez la commande
    2. Dites à Baker de cuire le pain et de vous en informer une fois terminé. Quand notifié:
      1. Charge de l'argent
      2. livrer du pain
      3. GOTO 1 - Next Client

        Mise à jour: J'ai refactore ce qui précède, il ressemble donc de manière plus étroitement un rappel. Vous, le caissier, frappera l'étape 3 immédiatement après avoir donné à la boulangerie. Vous toucherez l'étape 2.1 lorsque le boulanger vous avertit que le pain est prêt.

        De cette manière, vous livrez toujours autant de pain - vous ne pouvez vendre que de pain que votre boulanger peut cuire. Mais vous pouvez gérer vos clients de manière plus efficace, car au lieu d'attendre une commande de retour, vous commencez à gérer le prochain client.

        Maintenant, vous pouvez faire toutes sortes de fantaisie à ce sujet et charger l'argent au départ et dire au client de prendre le pain à l'autre bout du bureau, ou quelque chose comme ça. Je pense que Starbucks sont jolis "étirés" de cette manière. Le caissier prend la commande, émet un certain nombre de demandes de choses et indique au client d'attendre que tout se trouve dans la zone de ramassage. Super-efficacité.

        Maintenant, imaginez que votre ami commence à utiliser un autre enregistrement de caisse. Il suit votre exemple Async. Vous pouvez gérer plus de clients, encore plus rapidement! Notez que la seule chose que vous avez dû faire était de mettre votre ami là-bas et de lui donner votre flux de travail.

        Vous et votre ami sont deux boucles d'événements à une seule-filetage en parallèle. Ceci est analogique à deux processus nœud.js prenant des demandes. Vous n'avez pas besoin de faire quelque chose de complexe à paralléliser cela, vous venez de courir une boucle d'événement de plus.

        Ainsi, non, "les opérations en mouvement en fonctions" ne "pas automatiquement des processus enfants". Ils s'apparentent plus d'alarmes - quand cela est fini, notifiez-moi et laissez-moi prendre à ce stade, "Ce point" étant le code dans votre rappel. Mais le rappel sera toujours exécuté dans le même processus et le même thread.

        Maintenant, Node.js exploite également un pool de threads interne pour io. Ceci est résumé loin de vous: poursuivre l'analogie de boulangerie, disons que vous avez une "pool bouleuse" de Bakers - à vous, debout au caisse enregistreuse, vous n'avez pas à savoir à ce sujet. Vous venez de leur donner la commande ("un loaf de levain") et livrez cet ordre lorsque vous êtes informé que c'est fini. Mais les Bakers font cuire au pain en parallèle, dans leur propre "boulanger".


5 commentaires

Merci pour la grande analogie. Pour plus de précision. Dans cet exemple, un caissier prend une commande et le remet à la cuisine, il peut ensuite aller traiter une autre commande. Maintenant, le preneur de commande Voici la fonction de serveur principale .. La cuisine est toutes les fonctions déléguées avec leurs propres rappels. Si la fonction de serveur déléguette à une fonction et les tend un rappel et dit .. revenez-moi lorsque vous avez terminé .. Si la cuisine n'est pas donnée un moyen de frayer un processus d'enfant. Il ne bloquera-t-il pas encore jusqu'à ce que le rappel est envoyé?


La cuisine a une piscine de fil interne. Il aura donc des threads pour distribuer les tâches à. Mais ceci est résumé de vous dans Node.js, et rien de tout cela n'est exécuté dans votre boucle d'événement, de sorte que cela n'affectera pas du tout votre code. Si vous faites du calcul intensif de la CPU dans votre propre code, cela bloquera toute la boucle d'événement (comme, disons que le caissier décide de faire cuire l'un des loafs lui-même - pendant ce temps, il peut ' t prendre de nouvelles commandes).


Êtes-vous en train de dire que pour un nœud de fonction / rappel standard peut utiliser un pool de thread interne pour assurer des performances asynchrones, mais que pour un appel coûteux, nous aurions besoin de configurer un travailleur comme celui de l'exécutant? Si oui, comment puis-je connaître le seuil de «cher»? Cela semble être un point clé alors.


Exactement. Tant que vous utilisez des API asynchrones pour tous les IO (FS, DB, Network, etc.), vous êtes en sécurité. Mais si vous avez besoin de calculer un calcul coûteux, vous devez utiliser un processus d'enfant pour cela - ou même un scénario de piscine de travailleurs. Je ne peux pas vous donner un seuil, vous devez faire de l'analyse comparative / profilage pour découvrir des goulots d'étranglement.


@Linus, par API asynchrones Voulez-vous dire, ils ont tous leur piscine de fil interne, car seul moyen d'atteindre l'état non bloquant consiste à accumuler un nouveau fil, la question est de savoir si nous le faisons explicitement ou le cadre le fait pour nous. droite!!



4
votes

Je ne suis pas bien en anglais, je ne peux donc pas comprendre ce que vous voulez dire. Mais je peux dire que "Multi thread" et "asynchrones" sont un terme similaire mais différent. Même si un seul thread peut peformant comme «asynchrone».

Ce document n'est pas pour le nœud (c'est pour un cadre asynchrone Python "torsadé") Mais pourrait être utile pour vous.

Désolé pour mon mauvais anglais.


3 commentaires

C'est un excellent document que vous avez fourni, merci beaucoup! Très clair.


C'est un bon doc. Et votre anglais est bon aussi :)


L'article est précieux. Et votre anglais est génial :)



1
votes

La réponse acceptée est excellente, mais j'aimerais ajouter quelque chose de purement lié à non bloCing , en particulier cette partie de la question:

ou est-ce parce que les événements d'E / S ne font que bloquer fondamentalement les opérations signifient qu'ils saisissent le contrôle et ne le rendent pas jusqu'à ce que ...

L'IO asynchrone est possible même sans un cadre fournissant son propre bassin de threads de travailleurs io. En effet, cela peut être fait sans que le système multi-filetage tant que le système sous-jacent (opérationnel) fournit un mécanisme pour ne pas bloquer IO.

Généralement, cela se résume à un système d'appel du système comme celui de Posix's Sélectionnez (ou Microsoft Version du même ), ou plus de variations récentes sur la même idée comme Linux Epoll .

hypothétiquement parler, si nous voyons une fonction comme db.Query dans votre exemple, et en supposant que nous savons aussi que le cadre fournissant cette fonction pas s'appuie sur n'importe quel multi -threading, alors il est généralement sûr de conclure que:

  • Le framework tient une trace d'une liste globale de des descripteurs IO et rappel associés à tout autre -Blocking des demandes IO qui ont été initiées, comme votre dB.Query appel.
  • Le cadre a ou s'appuie sur une sorte de boucle d'événement d'application principale. Cela pourrait être n'importe quoi d'une vieille école tandis que (vrai) à quelque chose comme libev
  • Quelque part dans ladite boucle principale, Sélectionnez ou une fonction similaire sera utilisé pour vérifier si l'une de ces demandes d'IO en attente a fini encore.
  • Lorsqu'une demande IO finie est trouvée, son rappel associé est appelé, après quoi la boucle principale reprend.

    Un appel SQL DB comme votre dB.Query utilise probablement prise réseau io et non fichier io , mais de votre point de vue comme une application Les descripteurs de développeurs, de prise et de fichier sont traités de manière presque identique à de nombreux systèmes d'exploitation et peuvent tous deux être transmis à Sélectionner sur POSIX-Aime quand même.

    Il s'agit généralement de la manière dont les applications de serveur monopropulsifiées "jongleront" multiples connexions simultanées.


0 commentaires

0
votes

important - la db.Query () ne bloque pas la pile, car elle est exécutée ailleurs.

Exemple 1 blocs car la ligne 2 nécessite des informations à partir de la ligne 1 et doit donc attendre que la ligne 1 complète. La ligne 3 ne peut pas être exécutée avant la ligne 2 sous forme de code traitée par ordre de ligne, et donc la ligne 2 bloque la ligne 3 et toutes les autres lignes.

L'exemple 2 est non bloqué car la fonction de rappel n'est pas appelée à être exécutée jusqu'à la fin de la finition db.Quisery, il ne bloque donc pas de dossations () d'être exécutées.


0 commentaires