En los módulos de programación tradicionales, las operaciones de E/S son como una llamada de función local ordinaria: el programa se bloquea antes de que la función se ejecute y no pueda continuar ejecutándose. La E/S bloqueada se originó en el modelo de corte de tiempo anterior, donde cada proceso es como una persona independiente, con el propósito de distinguir a todos, y generalmente todos solo pueden hacer una cosa al mismo tiempo, y deben esperar a que se haga lo anterior antes de decidir qué hacer a continuación. Sin embargo, este modelo de "un usuario, un proceso" que se usa ampliamente en las redes informáticas e Internet es muy escalable. Al administrar múltiples procesos, consume mucha memoria y conmutación de contexto también ocupará muchos recursos. Estas son una gran carga para el sistema operativo, y a medida que aumenta el número de procesos, el rendimiento del sistema decae bruscamente.
Multithreading es una alternativa. Un hilo es un proceso liviano que comparte la memoria con otros hilos en el mismo proceso. Es más como una extensión del modelo tradicional, que se utiliza para ejecutar múltiples hilos simultáneamente. Cuando un hilo está esperando operaciones de E/S, otros hilos pueden hacerse cargo de la CPU. Cuando se complete la operación de E/S, se despertará el hilo que espera al frente. Es decir, un hilo en ejecución se puede interrumpir y luego reanudarse más tarde. Además, los hilos pueden ejecutarse en paralelo en diferentes núcleos de CPU de múltiples núcleos bajo algunos sistemas.
Los programadores no saben a qué hora se ejecutará el hilo. Deben tener cuidado de manejar el acceso concurrente a la memoria compartida, por lo que deben usar algunas primitivas de sincronización para sincronizar el acceso a una determinada estructura de datos, como el uso de cerraduras o semáforos, para obligar a los hilos a ejecutar en comportamientos y planes específicos. Las aplicaciones que dependen en gran medida del estado compartido entre hilos pueden tener fácilmente problemas extraños con una fuerte aleatoriedad y dificultad para encontrar.
Otra forma es usar una colaboración multiproceso, donde usted es responsable de liberar explícitamente la CPU y entregar el tiempo de CPU a otros hilos. Debido a que controla personalmente el plan de ejecución del hilo, la necesidad de sincronización se reduce, pero también aumenta la complejidad del programa y la posibilidad de errores, y no evita los problemas de múltiples subprocesos.
¿Qué es la programación basada en eventos?
La programación basada en eventos es un estilo de programación, donde los eventos determinan el proceso de ejecución de un programa. Los eventos se manejan por manejadores de eventos o devoluciones de llamada de eventos. Las devoluciones de llamada de eventos son funciones llamadas cuando ocurre un evento específico, como la base de datos devuelve el resultado de la consulta o el usuario hace clic en un botón.
Recuerde que en el modo tradicional de programación de E/S bloqueado, las consultas de bases de datos pueden verse así:
La copia del código es la siguiente:
result = Query ('Seleccionar * de Posts Where id = 1');
do_something_with (resultado);
La función de consulta anterior mantendrá el hilo o proceso actual en un estado de espera hasta que la base de datos subyacente complete la operación de consulta y devuelva.
En el modelo basado en eventos, esta consulta se convertirá en esta:
La copia del código es la siguiente:
query_finished = function (resultado) {
do_something_with (resultado);
}
consulta ('select * de publicaciones donde id = 1', query_finished);
Primero, define una función llamada Query_Finished, que contiene qué hacer después de completar la consulta. Luego pase esta función como un parámetro a la función de consulta. Query_Finished se llamará después de la ejecución de la consulta, en lugar de simplemente devolver el resultado de la consulta.
Cuando se produce un evento que le interesa, la función que define se llamará en lugar de simplemente devolver el valor del resultado. Este modelo de programación se llama programación impulsada por eventos o programación asincrónica. Esta es una de las características más obvias del nodo. Este modelo de programación significa que el proceso actual no se bloqueará al ejecutar operaciones de E/S. Por lo tanto, se pueden ejecutar múltiples operaciones de E/S en paralelo, y se llamará a la función de devolución de llamada correspondiente después de completar la operación.
La capa subyacente de programación basada en eventos se basa en bucles de eventos. Los bucles de eventos son básicamente una estructura en la que la detección de eventos y el procesador de eventos desencadenan la llamada continua de bucle de estas dos funciones. En cada bucle, el mecanismo de bucle de eventos debe detectar qué eventos ocurrieron. Cuando ocurre el evento, encuentra la función de devolución de llamada correspondiente y la llama.
El bucle de eventos es solo un hilo que se ejecuta en el proceso. Cuando ocurre un evento, el procesador de eventos puede ejecutarse solo y no se interrumpirá, es decir:
1. A lo sumo, la función de devolución de llamada de un evento se ejecuta en un momento específico
2. Ningún procesador de eventos se interrumpe cuando se ejecuta
Con esto, los desarrolladores ya no pueden tener dolores de cabeza sobre la sincronización de subprocesos y la modificación concurrente de la memoria compartida.
Un secreto bien conocido:
Hace mucho tiempo, las personas en la comunidad de programación de sistemas sabían que la programación impulsada por eventos era la mejor manera de crear servicios de altos servicios de concurrencia porque no tenía que ahorrar mucho contexto, por lo que ahorró mucha memoria, no tanto interruptor de contexto, y ahorró mucho tiempo de ejecución.
Poco a poco, este concepto impregnaba otras plataformas y comunidades, y surgieron algunas famosas implementaciones de bucles de eventos, como Ruby's Event Machine, Anyevnet de Perl y Twisted Python. Además de estos, hay muchas otras implementaciones e idiomas.
Para desarrollar estos marcos, debe aprender un conocimiento específico relacionado con el marco y las bibliotecas de clases específicas del marco. Por ejemplo, al usar Event Machine, para disfrutar de los beneficios del no bloqueo, debe evitar el uso de bibliotecas de clase síncronas y solo puede usar las bibliotecas de clase asíncronas de Event Machine. Si usa alguna biblioteca de bloqueo (como la biblioteca estándar de la mayoría de Ruby), su servidor pierde su escalabilidad óptima porque el bucle de eventos aún se bloqueará constantemente, bloqueando el procesamiento de eventos de E/S de vez en cuando.
El nodo se diseñó originalmente como una plataforma de servidor de E/S sin bloqueo, por lo que en general, debe esperar que todo el código que se ejecute en él no sea bloqueo. Debido a que JavaScript es muy pequeño y no obliga a ningún modelo de E/S (porque no tiene una biblioteca de clases de E/S estándar), el nodo está construido en un entorno muy puro y no habrá problemas heredados.
Cómo Node y JavaScript simplifican las aplicaciones asíncronas
El autor de Node, Ryan Dahl, usó inicialmente C para desarrollar este proyecto, pero descubrió que el contexto de mantener las llamadas de funciones era demasiado complejo, lo que resultó en una alta complejidad del código. Luego cambió a Lua, pero Lua ya tiene varias bibliotecas de E/S de bloqueo. La mezcla de bloqueo y no bloqueo puede confundir a los desarrolladores y, por lo tanto, evitar que muchas personas construyan aplicaciones escalables. Por lo tanto, Lua también fue abandonado por Dahl. Finalmente, recurrió a JavaScript, los cierres en JavaScript y las funciones de objetos de primer nivel, que hacen que JavaScript sea muy adecuado para la programación basada en eventos. La magia de JavaScript es una de las principales razones por las cuales el nodo es tan popular.
¿Qué es un cierre?
Un cierre puede entenderse como una función especial, pero puede heredar y acceder a variables en el alcance que se define. Cuando pasa una función de devolución de llamada como parámetro a otra función, se llamará más tarde. La magia es que cuando esta función de devolución de llamada se llama más adelante, en realidad recuerda el contexto en el que se define a sí mismo y a las variables en el contexto principal, y también puede acceder a ellas normalmente. Esta poderosa característica es el núcleo del éxito del nodo.
El siguiente ejemplo mostrará cómo funcionan los cierres de JavaScript en un navegador web. Si desea escuchar un evento independiente en un botón, puede hacer esto:
La copia del código es la siguiente:
var ClickCount = 0;
document.getElementById ('myButton'). onClick = function () {
clickCount += 1;
alerta ("Haga clic en" + ClickCount + "Times");
};
Así es como cuando se usa jQuery:
La copia del código es la siguiente:
var ClickCount = 0;
$ ('Botón#myButton'). Click (function () {
ClickedCount ++;
alerta ('Clicked' + ClickCount + 'Times.');
});
En JavaScript, las funciones son el primer tipo de objetos, lo que significa que puede aprobar funciones como parámetros a otras funciones. En los dos ejemplos anteriores, el primero asigna una función a otra función, y la segunda pasa la función como un parámetro a otra función. La función de procesamiento de eventos de clic (función de devolución de llamada) puede acceder a cada variable en el bloque de código donde la función lo define. En este ejemplo, puede acceder a la variable ClickCount definida en su cierre principal.
La variable ClickCount está en el alcance global (el alcance más externo en JavaScript), que guarda el número de veces que el usuario hace clic en un botón. Por lo general, es un mal hábito almacenar variables bajo el alcance global, porque es fácil entrar en conflicto con otro código, y debe poner variables en el alcance local donde las usa. La mayoría de las veces, solo envolver el código con una función es equivalente a crear otro cierre, lo que puede evitar fácilmente contaminar el entorno global, solo así:
La copia del código es la siguiente:
(función() {
var ClickCount = 0;
$ ('Botón#myButton'). Click (function () {
clickCount ++;
alerta ('Clicked' + ClickCount + 'Times.');
});
} ());
Nota: La séptima línea del código anterior define una función y la llama de inmediato. Este es un patrón de diseño común en JavaScript: cree un nuevo alcance creando una función.
Cómo los cierres ayudan a la programación asíncrona
En el modelo de programación basado en eventos, primero escriba el código para ejecutarse después de que ocurra el evento, luego coloque el código en una función y finalmente pase la función como un parámetro a la persona que llama, y luego llámelo por la función de llamadas más adelante.
En JavaScript, una función no es una definición aislada. También recuerda el contexto del alcance que se declara. Este mecanismo permite que las funciones de JavaScript accedan al contexto en el que se encuentra la definición de función y todas las variables en el contexto principal.
Cuando pasa una función de devolución de llamada como parámetro a la persona que llama, la función se llamará en algún punto posterior. Incluso si el alcance que define la función de devolución de llamada ha terminado, cuando se llama a la función de devolución de llamada, aún puede acceder a todas las variables en el alcance final y su alcance principal. Al igual que el último ejemplo, la función de devolución de llamada se llama dentro del clic () de jQuery, pero aún puede acceder a la variable ClickCount.
La magia de los cierres se muestra anteriormente. La aprobación de las variables de estado a una función le permite realizar una programación basada en eventos sin mantener los estados. El mecanismo de cierre de JavaScript lo ayudará a mantenerlos.
resumen
La programación basada en eventos es un modelo de programación que determina el proceso de ejecución del programa a través de la activación de eventos. Los programadores registran funciones de devolución de llamada para eventos que están interesados (generalmente llamados manejadores de eventos), y el sistema llama al controlador de eventos registrado cuando ocurre el evento. Este modelo de programación tiene muchas ventajas que los modelos de programación de bloqueo tradicionales no tienen. En el pasado, para implementar características similares, se debe utilizar multiprocesos/subprocesos múltiples.
JavaScript es un lenguaje poderoso debido a su primer tipo de funciones de objeto y propiedades de cierre, lo que lo hace muy adecuado para la programación basada en eventos.