javascript - ¿Por qué un bucle while bloquea el bucle de eventos?
node.js (5)
El siguiente ejemplo se da en un libro de Node.js:
var open = false;
setTimeout(function() {
open = true
}, 1000)
while (!open) {
console.log(''wait'');
}
console.log(''open sesame'');
Al explicar por qué el ciclo while bloquea la ejecución, el autor dice:
Node nunca ejecutará la devolución de llamada de tiempo de espera porque el bucle de eventos está atascado en esto mientras el bucle comenzó en la línea 7, ¡nunca le da la oportunidad de procesar el evento de tiempo de espera!
Sin embargo, el autor no explica por qué sucede esto en el contexto del bucle de eventos o lo que realmente sucede debajo del capó.
¿Alguien puede dar más detalles sobre esto?
¿Por qué el nodo se atasca?
¿Y cómo se podría cambiar el código anterior, mientras se retiene la estructura de control
while
para que el bucle de eventos no se bloquee y el código se comporte como se podría esperar razonablemente?
la espera se registrará solo 1 segundo antes de que se
setTimeout
y el proceso
setTimeout
después de registrar ''abrir sésamo''.
Las explicaciones genéricas, como las respuestas a esta pregunta sobre IO y los bucles de eventos y las devoluciones de llamada, realmente no me ayudan a racionalizar esto. Espero que una respuesta que haga referencia directa al código anterior ayude.
El nodo es una única tarea en serie. No hay paralelismo, y su concurrencia está ligada a IO. Piénselo de esta manera: todo se está ejecutando en un solo subproceso, cuando realiza una llamada IO que está bloqueando / sincronizando su proceso se detiene hasta que se devuelven los datos; sin embargo, digamos que tenemos un solo subproceso que, en lugar de esperar a IO (leer el disco, tomar una url, etc.), su tarea continúa con la siguiente tarea, y una vez completada esa tarea, verifica que IO. Esto es básicamente lo que hace el nodo, es un "bucle de eventos", su IO de sondeo para completar (o progresar) en un bucle. Entonces, cuando una tarea no se completa (su ciclo), el ciclo del evento no progresa. Para hacerlo mas simple.
Es bastante simple en realidad. Internamente, node.js consta de este tipo de bucle:
- Obtén algo de la cola del evento
- Ejecute cualquier tarea que se indique y ejecútela hasta que vuelva
- Cuando finalice la tarea anterior, obtenga el siguiente elemento de la cola de eventos
- Ejecute cualquier tarea que se indique y ejecútela hasta que vuelva
- Enjuague, haga espuma, repita - una y otra vez
Si en algún momento, no hay nada en la cola del evento, vaya a dormir hasta que se coloque algo en la cola del evento.
Entonces, si un fragmento de Javascript está en un bucle
while()
, entonces esa tarea no está terminando y según la secuencia anterior, no se seleccionará nada nuevo de la cola de eventos hasta que la tarea anterior esté completamente completada.
Por lo tanto, un ciclo
while()
muy largo o eterno simplemente agota el trabajo.
Debido a que Javascript solo ejecuta una tarea a la vez (un solo subproceso para la ejecución de JS), si esa tarea está girando en un ciclo while, entonces nada más puede ejecutarse.
Aquí hay un ejemplo simple que podría ayudar a explicarlo:
var done = false;
// set a timer for 1 second from now to set done to true
setTimeout(function() {
done = true;
}, 1000);
// spin wait for the done value to change
while (!done) { /* do nothing */}
console.log("finally, the done value changed!");
Algunos podrían pensar lógicamente que el ciclo while girará hasta que se dispare el temporizador y luego el temporizador cambiará el valor de
done
a
true
y luego el ciclo while terminará y se ejecutará
console.log()
al final.
Eso NO es lo que sucederá.
Esto realmente será un bucle infinito y la instrucción
console.log()
nunca se ejecutará.
El problema es que una vez que ingresa al giro espere en el ciclo
while()
, NO se puede ejecutar ningún otro Javascript.
Por lo tanto, el temporizador que desea cambiar el valor de la variable
done
no puede ejecutarse.
Por lo tanto, la condición del bucle while nunca puede cambiar y, por lo tanto, es un bucle infinito.
Esto es lo que sucede internamente dentro del motor JS:
-
done
variable inicializada afalse
-
setTimeout()
programa un evento de temporizador para 1 segundo a partir de ahora - El ciclo while comienza a girar
- 1 segundo en el ciclo while girando, el temporizador se dispara internamente al motor JS y la devolución de llamada del temporizador se agrega a la cola de eventos. Esto probablemente ocurre en un subproceso diferente, interno al motor JS.
-
El ciclo while sigue girando porque la variable
done
nunca cambia. Debido a que continúa girando, el motor JS nunca termina este hilo de ejecución y nunca puede extraer el siguiente elemento de la cola de eventos.
node.js es un entorno controlado por eventos.
Para resolver este problema en una aplicación del mundo real, la bandera de
done
se cambiaría en algún evento futuro.
Por lo tanto, en lugar de girar un ciclo while, debería registrar un controlador de eventos para algún evento relevante en el futuro y hacer su trabajo allí.
En el peor de los casos, puede configurar un temporizador recurrente y "sondear" para verificar el indicador con mucha frecuencia, pero en casi todos los casos, puede registrar un controlador de eventos para el evento real que hará que el indicador
done
cambie y haz tu trabajo en eso.
El código correctamente diseñado que sabe que otro código quiere saber cuándo algo ha cambiado puede incluso ofrecer su propio detector de eventos y sus propios eventos de notificación en los que uno puede registrar un interés o incluso una simple devolución de llamada.
Esta es una gran pregunta, pero encontré una solución.
var sleep = require(''system-sleep'')
var done = false
setTimeout(function() {
done = true
}, 1000)
while (!done) {
sleep(100)
console.log(''sleeping'')
}
console.log(''finally, the done value changed!'')
Creo que funciona porque el
system-sleep
no es una espera de giro.
Hay otra solucion. Puede obtener acceso al bucle de eventos en casi todos los ciclos.
let done = false;
setTimeout(() => {
done = true
}, 5);
const eventLoopQueue = () => {
return new Promise(resolve =>
setImmediate(() => {
console.log(''event loop'');
resolve();
})
);
}
const run = async () => {
while (!done) {
console.log(''loop'');
await eventLoopQueue();
}
}
run().then(() => console.log(''Done''));
porque el temporizador necesita regresar y está esperando que el ciclo termine para agregarse a la cola, por lo que aunque el tiempo de espera está en un hilo separado y puede terminar el temporizador, pero la "tarea" para configurar done = true está esperando en ese ciclo infinito para terminar