Node.js ahora se ha convertido en un miembro de la caja de herramientas para crear servicios de aplicaciones de red de alta concurrencia. ¿Por qué Node.js se ha convertido en el favorito del público? Este artículo comenzará con los conceptos básicos de procesos, subprocesos, corrutinas y modelos de E/S, y le brindará una introducción completa a Node.js y el modelo de concurrencia.
Generalmente llamamos proceso a una instancia en ejecución de un programa. Es una unidad básica para la asignación y programación de recursos por parte del sistema operativo. Generalmente incluye las siguientes partes:
进程表
. Cada proceso ocupa una进程表项
(también llamada进程控制块
). Esta entrada contiene estados importantes del proceso, como el contador del programa, el puntero de la pila, la asignación de memoria, el estado de los archivos abiertos y la información de programación. .Información para garantizar que después de suspender el proceso, el sistema operativo pueda reactivarlo correctamente.El proceso tiene las siguientes características:
Cabe señalar que si un programa se ejecuta dos veces, incluso si el sistema operativo puede permitirles compartir código (es decir, solo hay una copia del código en la memoria), no puede cambiar el hecho de que las dos instancias del programa en ejecución sean dos diferentes. procesa el hecho.
Durante la ejecución del proceso, debido a diversos motivos, como interrupciones y programación de la CPU, el proceso cambiará entre los siguientes estados:
En el diagrama de cambio de estado del proceso anterior, podemos ver que un proceso puede cambiar del estado en ejecución al estado listo y al estado bloqueado, pero solo el estado listo puede cambiar directamente al estado en ejecución. Esto se debe a que:
A veces, necesitamos utilizar subprocesos para resolver los siguientes problemas:
Con respecto a los subprocesos, necesitamos conocer los siguientes puntos:
Ahora que entendemos las características básicas de los hilos, hablemos de varios tipos de hilos comunes.
Los subprocesos de estado del kernel son subprocesos admitidos directamente por el sistema operativo. Sus características principales son las siguientes:
Los subprocesos en modo de usuario son subprocesos completamente construidos en el espacio del usuario. Sus características principales son las siguientes:
Un proceso liviano (LWP) es un subproceso de usuario creado y respaldado por el kernel. Sus características principales son las siguientes:
El espacio de usuario solo puede usar subprocesos del kernel a través de procesos livianos (LWP). puente entre los subprocesos en modo de usuario y los subprocesos del kernel. Por lo tanto, solo mediante el soporte de subprocesos del kernel puede haber un proceso liviano (LWP).
La mayoría de las operaciones de procesos livianos (LWP) requieren espacio en modo de usuario para iniciar la llamada al sistema. es relativamente costoso (requiere cambiar entre el modo de usuario y el modo de kernel)
cada proceso liviano (LWP) debe estar asociado con un subproceso del kernel específico, por lo tanto:
pueden acceder a sus propios procesos Todos los espacios de direcciones compartidos y recursos del sistema;
Arriba, presentamos brevemente los tipos de subprocesos comunes (subprocesos de estado del kernel, subprocesos de estado de usuario, procesos livianos). En el uso real, puede usarlos libremente según sus propias necesidades. combinación, como modelos comunes uno a uno, muchos a uno, muchos a muchos y otros. Debido a limitaciones de espacio, este artículo no presentará demasiado sobre esto. Los estudiantes interesados pueden estudiarlo por sí mismos.
, también llamado Fiber, es un mecanismo de ejecución de programas basado en subprocesos que permite a los desarrolladores administrar la programación de ejecución, el mantenimiento del estado y otros comportamientos por sí mismos. Sus características principales son
En JavaScript, async/await
que usamos con frecuencia es una implementación de corrutina, como el siguiente ejemplo:
function updateUserName(id, name) { usuario constante = getUserById(id); usuario.updateName(nombre); devolver verdadero; } función asíncrona updateUserNameAsync(id, nombre) { usuario constante = esperar getUserById(id); espere usuario.updateName(nombre); devolver verdadero; }
En el ejemplo anterior, la secuencia de ejecución lógica dentro de las funciones updateUserName
y updateUserNameAsync
es:
getUserById
y asignar su valor de retorno a la variable user
;updateName
del user
true
La principal diferencia entre los dos radica en el control de estado durante la operación real:
updateUserName
, se ejecuta en secuencia de acuerdo con la secuencia lógica mencionada anteriormente;updateUserNameAsync
, también se ejecuta en secuencia de acuerdo con; La secuencia lógica mencionada anteriormente, pero cuando se encuentre con await
, updateUserNameAsync
se suspenderá y guardará el estado actual del programa en la ubicación suspendida. No volverá a activar updateUserNameAsync
hasta que el fragmento del programa después de await
regrese y restaure el estado del programa antes de suspender. y luego Continuar con el siguiente programa.Del análisis anterior, podemos adivinar audazmente: lo que las corrutinas deben resolver no son los problemas de concurrencia del programa que los procesos y subprocesos deben resolver, sino los problemas encontrados al procesar tareas asincrónicas (como operaciones de archivos, solicitudes de red, etc.); Antes de async/await
, solo podíamos manejar tareas asincrónicas a través de funciones de devolución de llamada, lo que fácilmente podría hacernos caer en回调地狱
y producir un desorden de código que generalmente es difícil de mantener. A través de rutinas, podemos lograr la sincronización del código asincrónico. .
Lo que hay que tener en cuenta es que la capacidad principal de las corrutinas es poder suspender un determinado programa y mantener el estado de la posición de suspensión del programa, y reanudarlo en la posición suspendida en algún momento en el futuro, y continuar ejecutar el siguiente segmento después de la posición de suspensión.
Una operación I/O
completa debe pasar por las siguientes etapas:
I/O
al kernel a través de una llamada al sistema,I/O
(dividida; en la etapa de preparación y la fase de ejecución real), y devuelve los resultados del procesamiento al hilo del usuario.Podemos dividir aproximadamente las operaciones I/O
en cuatro tipos:阻塞I/O
,非阻塞I/O
,同步I/O
y异步I/O
Antes de analizar estos tipos, primero nos familiarizaremos con los siguientes dos conjuntos de operaciones. conceptos (aquí se supone que el servicio A llama al servicio B):
阻塞/非阻塞
:
阻塞调用
非阻塞调用
.同步/异步
:
同步
.回调
una vez completada la ejecución; El resultado se notifica a A, luego el servicio B es异步
.Muchas personas a menudo confunden阻塞/非阻塞
con同步/异步
, por lo que se debe prestar especial atención:
阻塞/非阻塞
es para调用者
al servicio;同步/异步
es para被调用者
del servicio.Después de comprender阻塞/非阻塞
y同步/异步
, veamos el I/O 模型
específico.
: después de que el subproceso del usuario inicia una llamada al sistema I/O
, el subproceso del usuario se阻塞
inmediatamente hasta que se procese toda la operación I/O
y el resultado se devuelva al subproceso del usuario. El proceso (hilo) puede liberar阻塞
y continuar realizando operaciones posteriores.
Características:
I/O
, el proceso del usuario (hilo) no puede realizar otras operaciones,I/O
puede bloquear el subproceso entrante (subproceso), por lo que para responder a la solicitud I/O
a tiempo, es necesario asignar un subproceso entrante (subproceso) a cada solicitud, lo que generará una gran cantidad de recursos. uso, y para solicitudes de conexión largas, dado que los recursos entrantes (subprocesos) no se pueden liberar durante mucho tiempo, si hay nuevas solicitudes en el futuro, se producirá un cuello de botella grave en el rendimiento.Definición
I/O
en un subproceso (subproceso), si la operación I/O
no está lista, la llamada de I/O
devolverá un error y el usuario no necesita ingresar al hilo (hilo). Espere, pero use el sondeo para detectar si la operación I/O
está listaI/O
real bloqueará el hilo del usuario hasta que el resultado de la ejecución se devuelva al hilo. hilo del usuario.Características:
I/O
(generalmente usando un bucle while
), el modelo necesita ocupar la CPU y consumir recursos de la CPUI/O
esté lista, el usuario; necesita ingresar (El subproceso) el subproceso no se bloqueará cuando la operación I/O
esté lista, las operaciones I/O
reales posteriores impedirán que el usuario ingrese al subproceso (subproceso).Después de que el proceso de usuario (subproceso) inicia una llamada al sistema I/O
, si la llamada I/O
provoca que el proceso de usuario (subproceso) se bloquee, entonces la llamada I/O
es同步I/O
de lo contrario es异步I/O
.
El criterio para juzgar si una operación I/O
同步
o异步
es el mecanismo de comunicación entre los subprocesos del usuario y las operaciones I/O
. En el
同步
, la interacción entre los subprocesos del usuario y I/O
se sincroniza a través del búfer del núcleo. es decir, el kernel sincronizará los resultados de la ejecución de la operación de I/O
con el búfer y luego copiará los datos en el búfer al subproceso del usuario. Este proceso bloqueará el subproceso del usuario hasta que la operación I/O
se complete异步
I/O
se sincroniza directamente a través del kernel, es decir, el kernel copiará directamente los resultados de la ejecución de la operación de I/O
al hilo del usuario (hilo). no bloquear el proceso (hilo) del usuario.Node.js utiliza un modelo de I/O
asíncrono controlado por eventos de un solo subproceso. Personalmente, creo que la razón para elegir este modelo es:
I/O
. Cómo gestionar de manera razonable y eficiente recursos de subprocesos múltiples garantizando al mismo tiempo una alta concurrencia es más complicado que la gestión de recursos de un solo subproceso.En resumen, con fines de simplicidad y eficiencia, Node.js adopta un modelo I/O
asíncrono controlado por eventos de un solo subproceso e implementa su modelo a través del EventLoop del subproceso principal y el subproceso de trabajo auxiliar:
Cabe señalar que Node.js no es adecuado para realizar tareas que requieren un uso intensivo de la CPU (es decir, que requieren muchos cálculos); esto se debe a que el código EventLoop y JavaScript (código de tarea de evento no asíncrono) se ejecutan en el mismo subproceso; el hilo principal), y cualquiera de ellos Si uno se ejecuta durante demasiado tiempo, puede causar que el hilo principal se bloquee. Si la aplicación contiene una gran cantidad de tareas que requieren una ejecución prolongada, reducirá el rendimiento del servidor e incluso puede. hacer que el servidor deje de responder.
Node.js es una tecnología que los desarrolladores front-end deben enfrentar ahora e incluso en el futuro. Sin embargo, la mayoría de los desarrolladores front-end solo tienen un conocimiento superficial de Node.js para que todos comprendan mejor el modelo de concurrencia de Node. .js. Este artículo presenta primero procesos, subprocesos y corrutinas, luego presenta diferentes modelos I/O
y, finalmente, ofrece una breve introducción al modelo de concurrencia de Node.js. Aunque no hay mucho espacio para presentar el modelo de concurrencia de Node.js, el autor cree que nunca se puede separar de los principios básicos. Dominar los conceptos básicos relevantes y luego comprender profundamente el diseño y la implementación de Node.js obtendrá el doble de resultado. con la mitad del esfuerzo.
Finalmente, si hay algún error en este artículo, espero que puedan corregirlo. Les deseo a todos una feliz codificación todos los días.