La popularidad masiva del término "asincrónico" estaba en la ola de la Web 2.0, que barrió la web con JavaScript y Ajax. Pero el asincrónico es raro en la mayoría de los lenguajes de programación de alto nivel. PHP refleja mejor esta característica: no solo bloquea asincrónicamente, sino que tampoco proporciona múltiples hilos. PHP se ejecuta de manera síncrona de bloqueo. Tales ventajas son beneficiosas para que los programadores escriban la lógica comercial en secuencia, pero en las complejas aplicaciones de red, el bloqueo hace que no sea más concurrente.
En el lado del servidor, la E/S es muy costosa y la E/S distribuida es más costosa. Solo cuando el backend puede responder rápidamente a los recursos puede mejorar la experiencia del front-end. Node.js es la primera plataforma en usar asíncrono como el método de programación principal y el concepto de diseño. Acompañado de E/S asíncrono, impulsado por eventos y un solo hilo, forman el tono de nodo. Este artículo presentará cómo el nodo implementa la E/S asíncrona.
1. Conceptos básicos
"Async" y "no bloqueado" suenan lo mismo, y en términos de resultados reales, ambos logran el propósito del paralelismo. Pero desde la perspectiva de la E/S del núcleo informático, solo hay dos maneras: bloqueo y no bloqueo. Por lo tanto, asíncrono/sincrónico y bloqueo/no bloqueo son en realidad dos cosas diferentes.
1.1 Bloqueo de E/S y E/S sin bloqueo
Una característica del bloqueo de E/S es que después de llamar, debe esperar hasta que todas las operaciones se completen en el nivel del núcleo del sistema antes de que se termine la llamada. Tomando leer un archivo en el disco como ejemplo, esta llamada finaliza después de que el kernel del sistema completa la búsqueda en el disco, lee datos y copia datos en la memoria.
El bloqueo de E/S hace que la CPU espere E/S, perder el tiempo de espera y la potencia de procesamiento de la CPU no se puede utilizar por completo. La característica de la E/S sin bloqueo es que volverá inmediatamente después de la llamada, y la porción de tiempo de la CPU se puede usar para manejar otras transacciones después de la devolución. Dado que la E/S completa no se completa, lo que se devuelve de inmediato no son los datos esperados por la capa comercial, sino solo el estado de la llamada actual. Para obtener los datos completos, la solicitud debe llamar repetidamente a la operación de E/S para confirmar si está completado (es decir, encuesta). Las técnicas de votación necesitan lo siguiente:
1.Reir: Verificar el estado de E/S por llamadas repetidas es el método de rendimiento más original y más bajo
2. Seleccione: mejoras para leer, juzgar el estado del evento en el descriptor de archivo. La desventaja es que el número máximo de descriptores de archivos es limitado.
3.Poll: mejoras para seleccionar, utilizando listas vinculadas para evitar el límite de número máximo, pero cuando hay muchos descriptores, el rendimiento sigue siendo muy bajo
4.Poll: si no se verifica ningún evento de E/S durante las encuestas, dormirá hasta que ocurra el evento y lo despertará. Este es el mecanismo de notificación de eventos de E/S más eficiente en Linux.
Las encuestas satisfacen la necesidad de E/S sin bloqueo para garantizar la adquisición completa de datos, pero para las aplicaciones aún puede contar como una especie de sincronización porque aún necesita esperar a que las E/S regresen por completo. Durante la espera, la CPU se usa para atravesar el estado del descriptor del archivo o para hibernar esperando que ocurran los eventos.
1.2 E/S asíncrono en ideal y realidad
La E/S asíncrona perfecta debe ser la aplicación que inicia una llamada sin bloqueo, y puede manejar directamente la siguiente tarea sin sondear, simplemente pase los datos a la aplicación a través de una señal o devolución de llamada después de que se complete la E/S.
En realidad, la E/S asíncrona tiene diferentes implementaciones bajo diferentes sistemas operativos. Por ejemplo, *Nix Platform adopta un grupo de subprocesos personalizado, mientras que la plataforma Windows adopta un modelo IOCP. Node proporciona Libuv como una capa de encapsulación abstracta para encapsular los juicios de compatibilidad de la plataforma, y garantiza que la implementación de E/S asíncrona del nodo superior y las plataformas inferiores sea independiente. Debe enfatizarse que a menudo mencionamos que el nodo es un solo hilo, lo que solo significa que la ejecución de JavaScript está en un solo hilo, y hay otros grupos de hilos que realmente completan las tareas de E/S dentro del nodo.
2. E/S asincrónica del nodo
2.1 bucle de eventos
El modelo de ejecución de Node es en realidad un bucle de eventos. Cuando comienza el proceso, el nodo crea un bucle infinito, y cada proceso de ejecutar el cuerpo del bucle se convierte en una marca. Cada proceso de marca es verificar si hay eventos esperando ser procesados. Si es así, se eliminarán los eventos y sus funciones de devolución de llamada relacionadas. Si hay funciones de devolución de llamada asociadas, se ejecutarán y luego se ingresará el siguiente bucle. Si no hay más procesamiento de eventos, salga del proceso.
2.2 Observador
Hay varios observadores en cada bucle de eventos, y al preguntar a estos observadores, podemos determinar si hay eventos que se procesan. El bucle de eventos es un modelo de productor/consumidor típico. En el nodo, los eventos provienen principalmente de solicitudes de red, E/S de archivos, etc. Estos eventos tienen observadores de E/S de red correspondientes, observadores de E/S de archivos, etc. El bucle de eventos elimina el evento del observador y lo procesa.
2.3 Objeto de solicitud
Durante la transición de JavaScript al núcleo que realiza operaciones de E/S, hay un producto intermedio llamado objeto de solicitud. Tomando el método más simple de fs.open () en Windows (abra un archivo y obtenga un descriptor de archivo de acuerdo con la ruta y los parámetros especificados) como ejemplo, desde las llamadas JS hasta los módulos incorporados, las llamadas del sistema a través de Libuv en realidad se denomina método UV_FS_OPEN (). Durante el proceso de llamada, se crea un objeto de solicitud FSREQWrap, y los parámetros y métodos pasados de la capa JS se encapsulan en este objeto de solicitud. La función de devolución de llamada que más nos preocupa se establece en la propiedad ONCOMPETE_SYM de este objeto. Una vez envuelto el objeto, empuje el objeto FSREQWrap en la piscina de subprocesos y espere la ejecución.
En este punto, la llamada JS regresa inmediatamente, y el hilo JS puede continuar realizando operaciones posteriores. La operación de E/S actual está esperando la ejecución en el grupo de subprocesos, que completa la primera etapa de la llamada asíncrona.
2.4 Ejecutar devoluciones de llamada
La notificación de devolución de llamada es la segunda fase de E/S asincrónica. Después de llamar a la operación de E/S en el grupo de subprocesos, los resultados obtenidos se almacenarán y luego se notifica a IOCP de que la operación del objeto actual se ha completado y el hilo devuelve el grupo de subprocesos. Durante cada ejecución de garrapatas, el observador de E/S del bucle de eventos llamará al método relevante para verificar si hay solicitudes completadas en el grupo de subprocesos. Si existe, el objeto de solicitud se agregará a la cola de E/S Observador y luego se procesará como un evento.
3. API asincrónica no i/o
También hay algunas API asincrónicas que no están relacionadas con E/S en el nodo, como Timers SetTimeout (), SetInterval (), Process.NextTick () y SetImmdiate () que ejecutan inmediatamente tareas de forma asincrónica, etc., que se introducirán brevemente aquí.
3.1 API de temporizador
Las API en el lado del navegador de SetTimeOut () y SetInterval () son consistentes. Su principio de implementación es similar a la E/S asíncrona, pero no requieren la participación del grupo de hilos de E/S. El temporizador creado llamando a la API del temporizador se insertará en un árbol rojo y negro dentro del observador del temporizador. La marca de cada bucle de eventos iterará el objeto del temporizador del árbol rojo y negro para verificar si ha excedido el tiempo de tiempo. Si excede, se formará un evento y la función de devolución de llamada se ejecutará de inmediato. El principal problema con un temporizador es que su tiempo de tiempo no es particularmente preciso (milisegundos, dentro de la tolerancia).
3.2 API de ejecución de tareas asincrónicas
Antes de que apareciera el nodo, muchas personas podrían llamar a esto para realizar inmediatamente una tarea de forma asincrónica:
La copia del código es la siguiente:
setTimeOut (function () {
// HACER
}, 0);
Debido a las características de los bucles de eventos, el temporizador no es lo suficientemente preciso, y el uso de un árbol rojo y negro requiere el uso de un temporizador, y la complejidad de varios tiempos de operación es o (log (n)). El método Process.NextTick () solo colocará la función de devolución de llamada en la cola y la sacará y la ejecutará en la siguiente ronda de tick. La complejidad es O (1) y es más eficiente.
Además, hay un método setimMediate () similar al método anterior, ambos retrasando la ejecución de la función de devolución de llamada. Sin embargo, el primero tiene una prioridad más alta que la segunda, porque el bucle de eventos verifica el observador en secuencia. Además, la función de devolución de llamada anterior se guarda en una matriz, y cada ronda de tick ejecutará todas las funciones de devolución de llamada en la matriz; El último resultado se guarda en una lista vinculada, y cada ronda de tick solo ejecutará una función de devolución de llamada.
4. Servidores impulsados por eventos y de alto rendimiento
El ejemplo anterior ilustra cómo el nodo implementa la E/S asincrónica. De hecho, Node también aplica E/S asincrónica para el procesamiento de sockets de red, que también es la base de que Node crea un servidor web. Los modelos clásicos del servidor son:
1. Síncrono: solo se puede procesar una solicitud a la vez, y el resto de las solicitudes están en estado de espera
2. Por proceso/por solicitud: Inicie un proceso para cada solicitud, pero los recursos del sistema son limitados y no tienen escalabilidad.
3. Por subproceso/por solicitud: Inicie un hilo para cada solicitud. Los hilos son más ligeros que los procesos, pero cada hilo ocupa una cierta cantidad de memoria. Cuando lleguen grandes solicitudes concurrentes, la memoria se agotará pronto.
El famoso Apache adopta la forma por hilo/por solicitud, por lo que es difícil hacer frente a una alta concurrencia. El nodo maneja las solicitudes a través de métodos basados en eventos, que pueden guardar la sobrecarga de crear y destruir hilos. Al mismo tiempo, el sistema operativo tiene menos hilos al programar tareas, y el costo del cambio de contexto también es muy bajo. El nodo puede manejar las solicitudes de manera ordenada incluso con una gran cantidad de conexiones.
El bien conocido servidor NGINX también abandona el método de múltiples subprocesos y adopta el mismo método basado en eventos que el nodo. Ahora Nginx está en gran medida para reemplazar a Apache. Nginx está escrito en C Pure C y tiene un alto rendimiento, pero solo es adecuado para servidores web, se utiliza para el proxy inverso o el equilibrio de carga, etc. El nodo puede construir las mismas funciones que NGINX, y también puede manejar varios negocios específicos, y su propio rendimiento también es bueno. En proyectos reales, podemos combinarlos para lograr el mejor rendimiento de la aplicación.