Node.js est désormais devenu membre de la boîte à outils permettant de créer des services d'applications réseau à haute concurrence. Pourquoi Node.js est-il devenu le chouchou du public ? Cet article commencera par les concepts de base des processus, des threads, des coroutines et des modèles d'E/S, et vous donnera une introduction complète à Node.js et au modèle de concurrence.
Nous appelons généralement une instance en cours d'exécution d'un programme un processus. Il s'agit d'une unité de base pour l'allocation et la planification des ressources par le système d'exploitation. Elle comprend généralement les parties suivantes :
进程表
processus occupe une进程表项
(également appelée进程控制块
). Cette entrée contient des états de processus importants tels que le compteur de programme, le pointeur de pile, l'allocation de mémoire, l'état des fichiers ouverts et les informations de planification. . informations pour garantir qu'une fois le processus suspendu, le système d'exploitation peut relancer correctement le processus.Le processus présente les caractéristiques suivantes :
Il convient de noter que si un programme est exécuté deux fois, même si le système d'exploitation peut leur permettre de partager du code (c'est-à-dire qu'une seule copie du code est en mémoire), cela ne peut pas changer que les deux instances du programme en cours d'exécution soient deux instances différentes. traite les faits.
Pendant l'exécution du processus, pour diverses raisons telles que les interruptions et la planification du processeur, le processus basculera entre les états suivants :
À partir du diagramme de commutation d'état de processus ci-dessus, nous pouvons voir qu'un processus peut passer de l'état d'exécution à l'état prêt et à l'état bloqué, mais que seul l'état prêt peut être directement commuté à l'état d'exécution. En effet,
Parfois, nous devons utiliser des threads pour résoudre les problèmes suivants :
Concernant les threads, nous devons connaître les points suivants :
Maintenant que nous comprenons les caractéristiques de base des threads, parlons de plusieurs types de threads courants.
Les threads d'état du noyau sont des threads directement pris en charge par le système d'exploitation. Ses principales caractéristiques sont les suivantes :
Les threads en mode utilisateur sont des threads entièrement construits dans l'espace utilisateur. Ses principales caractéristiques sont les suivantes :
Un processus léger (LWP) est un thread utilisateur construit et pris en charge par le noyau. Ses principales caractéristiques sont les suivantes :
L'espace utilisateur ne peut utiliser les threads du noyau que via des processus légers (LWP). pont entre les threads en mode utilisateur et les threads du noyau. Par conséquent, ce n'est qu'en prenant en charge les threads du noyau qu'il peut y avoir un processus léger (LWP).
La plupart des opérations des processus légers (LWP) nécessitent de l'espace en mode utilisateur pour lancer l'appel système. est relativement coûteux (nécessite de basculer entre le mode utilisateur et le mode noyau) ;
chaque processus léger (LWP) doit être associé à un thread de noyau spécifique, par conséquent :
ils peuvent accéder à leurs propres processus. Tous les espaces d'adressage et ressources système partagés.
Ci-dessus, nous avons brièvement présenté les types de threads courants (threads d'état du noyau, threads d'état utilisateur, processus légers). Chacun d'eux a son propre champ d'application. En utilisation réelle, vous pouvez les utiliser librement selon vos propres besoins. combinaison, telle que les modèles communs un-à-un, plusieurs-à-un, plusieurs-à-plusieurs et autres. En raison du manque d'espace, cet article n'introduira pas grand-chose à ce sujet.
, également appelé Fibre, est un mécanisme d'exécution de programme construit sur des threads qui permet aux développeurs de gérer eux-mêmes la planification de l'exécution, la maintenance de l'état et d'autres comportements. Ses principales caractéristiques sont les suivantes
En JavaScript, async/await
que nous utilisons souvent est une implémentation de coroutine, comme dans l'exemple suivant :
function updateUserName(id, name) { const utilisateur = getUserById(id); user.updateName(nom); renvoie vrai ; } fonction asynchrone updateUserNameAsync (id, nom) { const utilisateur = attendre getUserById(id); attendre user.updateName(name); renvoie vrai ; }
Dans l'exemple ci-dessus, la séquence d'exécution logique au sein des fonctions updateUserName
et updateUserNameAsync
est :
getUserById
et attribuer sa valeur de retour à la variable user
;updateName
de user
true
La principale différence entre les deux réside dans le contrôle d'état pendant le fonctionnement réel :
updateUserName
, elle est exécutée en séquence selon la séquence logique mentionnée ci-dessus,updateUserNameAsync
, elle est également exécutée en séquence selon ; la séquence logique mentionnée ci-dessus, mais en cas de rencontre avec await
, updateUserNameAsync
sera suspendu et enregistrera l'état actuel du programme à l'emplacement suspendu. Il ne réveillera pas updateUserNameAsync
jusqu'à ce que le fragment de programme après await
revienne et restaure l'état du programme avant la suspension. puis passez au programme suivant.À partir de l'analyse ci-dessus, nous pouvons deviner avec audace : ce que les coroutines doivent résoudre, ce ne sont pas les problèmes de concurrence de programme que les processus et les threads doivent résoudre, mais les problèmes rencontrés lors du traitement des tâches asynchrones (telles que les opérations sur les fichiers, les requêtes réseau, etc.) ; Dans Avant async/await
, nous ne pouvions gérer les tâches asynchrones que via des fonctions de rappel, ce qui pourrait facilement nous faire tomber dans回调地狱
et produire un désordre de code généralement difficile à maintenir. Grâce aux coroutines, nous pouvons réaliser la synchronisation du code asynchrone. .
Ce qu'il faut garder à l'esprit, c'est que la capacité principale des coroutines est d'être capable de suspendre un certain programme et de maintenir l'état de la position de suspension du programme, et de le reprendre à la position suspendue à un moment donné dans le futur, et de continuer à exécuter le segment suivant après le programme de position de suspension.
Une opération I/O
complète doit passer par les étapes suivantes :
I/O
au noyau via un appel système,I/O
(divisée) ; dans la phase de préparation et la phase d'exécution proprement dite), et renvoie les résultats du traitement au thread utilisateur.Nous pouvons grossièrement diviser les opérations I/O
en quatre types :阻塞I/O
,非阻塞I/O
,同步I/O
et异步I/O
Avant d'aborder ces types, nous nous familiarisons d'abord avec les deux ensembles suivants. concepts (ici Supposons que le service A appelle le service B) :
阻塞/非阻塞
:
阻塞调用
非阻塞调用
.同步/异步
:
同步
;回调
une fois l'exécution terminée. . Le résultat est notifié à A, alors le service B est异步
.Beaucoup de gens confondent souvent阻塞/非阻塞
avec同步/异步
, il faut donc prêter une attention particulière :
阻塞/非阻塞
est pour调用者
du service ;同步/异步
est pour被调用者
du service.Après avoir compris阻塞/非阻塞
et同步/异步
, examinons le I/O 模型
spécifique.
: une fois que le thread utilisateur a lancé un appel système I/O
, le thread utilisateur sera immédiatement阻塞
jusqu'à ce que l'ensemble de l'opération I/O
soit traitée et que le résultat soit renvoyé au thread utilisateur seulement après que l'utilisateur entre dans le processus. (thread) peut libérer阻塞
et continuer à effectuer les opérations ultérieures.
Caractéristiques :
I/O
, le processus (thread) de l'utilisateur ne peut pas effectuer d'autres opérations,I/O
peut bloquer le thread (thread) entrant, donc afin de répondre à la requête I/O
à temps, il est nécessaire d'allouer un thread (thread) entrant à chaque requête, ce qui entraînera d'énormes ressources utilisation et pour les demandes de connexion longues, étant donné que les ressources entrantes (thread) ne peuvent pas être libérées pendant une longue période, s'il y a de nouvelles demandes à l'avenir, un sérieux goulot d'étranglement des performances se produira.:
I/O
dans un thread (thread), si l'opération I/O
n'est pas prête, l'appel I/O
renverra une erreur et l'utilisateur ne le fera pas. vous devez entrer dans le thread (thread). Attendez, mais utilisez l'interrogation pour détecter si l'opération I/O
est prête ;I/O
réelle bloquera le thread de l'utilisateur jusqu'à ce que le résultat de l'exécution soit renvoyé au fil de discussion de l'utilisateur.Caractéristiques :
I/O
(généralement à l'aide d'une boucle while
), le modèle doit occuper le CPU et consommer les ressources du CPUI/O
ne soit prête, l'utilisateur ; doit entrer (Le thread) le thread ne sera pas bloqué Lorsque l'opération I/O
est prête, les opérations I/O
réelles ultérieures empêcheront l'utilisateur d'entrer dans le thread (thread) ;Une fois que le processus utilisateur (thread) a lancé un appel système I/O
, si l'appel I/O
provoque le blocage du processus utilisateur (thread), alors l'appel I/O
est同步I/O
sinon il s'agit异步I/O
.
Le critère pour juger si une opération I/O
同步
ou异步
est le mécanisme de communication entre les threads utilisateur et les opérations I/O
. Dans le
同步
, l'interaction entre les threads utilisateur et I/O
est synchronisée via le tampon du noyau. c'est-à-dire que le noyau synchronisera les résultats d'exécution de l'opération I/O
avec le tampon, puis copiera les données dans le tampon dans le thread utilisateur. Ce processus bloquera le thread utilisateur jusqu'à ce que l'opération I/O
soit terminée异步
I/O
est directement synchronisée via le noyau, c'est-à-dire que le noyau copiera directement les résultats d'exécution de l'opération I/O
dans le thread utilisateur (thread). ne bloque pas le processus (thread) de l'utilisateur.Node.js utilise un modèle I/O
asynchrone à thread unique et piloté par événements. Personnellement, je pense que la raison du choix de ce modèle est la suivante :
I/O
. Comment gérer de manière raisonnable et efficace des ressources multithread tout en garantissant une concurrence élevée est plus compliqué que la gestion de ressources monothread.En bref, dans un souci de simplicité et d'efficacité, Node.js adopte un modèle I/O
asynchrone à thread unique et piloté par événements, et implémente son modèle via l'EventLoop du thread principal et le thread de travail auxiliaire :
Il convient de noter que Node.js n'est pas adapté à l'exécution de tâches gourmandes en CPU (c'est-à-dire nécessitant beaucoup de calculs) ; en effet, le code EventLoop et JavaScript (code de tâche d'événement non asynchrone) s'exécute dans le même thread (c'est-à-dire : le thread principal), et n'importe lequel d'entre eux. Si l'on s'exécute trop longtemps, cela peut provoquer le blocage du thread principal. Si l'application contient un grand nombre de tâches nécessitant une exécution longue, cela réduira le débit du serveur et peut même. faire en sorte que le serveur ne réponde plus.
Node.js est une technologie à laquelle les développeurs front-end doivent faire face aujourd'hui et même à l'avenir. Cependant, la plupart des développeurs front-end n'ont qu'une connaissance superficielle de Node.js afin de permettre à chacun de mieux comprendre le modèle de concurrence. .js, cet article présente d'abord les processus, les threads et les coroutines, puis présente différents modèles I/O
et donne enfin une brève introduction au modèle de concurrence de Node.js. Bien qu'il n'y ait pas beaucoup d'espace pour présenter le modèle de concurrence Node.js, l'auteur estime qu'il ne peut jamais être séparé des principes de base, puis maîtriser les bases pertinentes, puis comprendre en profondeur la conception et la mise en œuvre de Node.js obtiendra deux fois le résultat. avec la moitié de l'effort.
Enfin, s’il y a des erreurs dans cet article, j’espère que vous pourrez les corriger. Je vous souhaite à tous un bon codage chaque jour.