script pagina how funciones funcion ejecutar defer cargar carga async asincronas asincrona antes javascript node.js asynchronous parallel-processing async.js

javascript - pagina - Async.js-¿Es el paralelo realmente paralelo?



javascript src async defer (5)

¿Cómo puede Async.js ejecutar tareas en paralelo en un solo hilo? Me estoy perdiendo de algo.

parallel ejecuta todas sus tareas simultáneamente. Por lo tanto, si sus tareas contienen llamadas de E / S (por ejemplo, consultar DB), aparecerán como si se hubieran procesado en paralelo.

¿Cómo está esto habilitado en un solo hilo? eso es lo que no pude entender.

Node.js no bloquea. Entonces, en lugar de manejar todas las tareas en paralelo, cambia de una tarea a otra. Por lo tanto, cuando la primera tarea hace que la llamada de E / S esté inactiva, Node.js simplemente cambia al procesamiento de otra.

Las tareas de E / S pasaron la mayor parte de su tiempo de procesamiento esperando el resultado de la llamada de E / S. Al bloquear lenguajes como Java, dicha tarea bloquea su hilo mientras espera los resultados. Pero Node.js utiliza su tiempo para procesar otras tareas en lugar de esperar.

entonces eso significa que si el procesamiento interno de cada tarea es asincrónico, el hilo se concede a cada bit de estas tareas, independientemente de si alguno de ellos ha finalizado o no hasta que todos hayan terminado sus bits.

Sí, es casi como dijiste. Node.js comienza a procesar la primera tarea hasta que hace una pausa para hacer una llamada de E / S. En ese momento, Node.js lo abandona y concede su hilo principal a otra tarea. Por lo tanto, puede decir que el hilo se concede a cada tarea activa sucesivamente.

Como he entendido hasta ahora: Javascript tiene un solo hilo. Si difiere la ejecución de algún procedimiento, simplemente programe (póngalo en cola) para que se ejecute la próxima vez que el hilo esté libre. Pero Async.js define dos métodos: Async::parallel & Async::parallelLimit , y cito textualmente:

  • paralelo (tareas, [devolución de llamada])

Ejecute una serie de funciones en paralelo, sin esperar hasta que la función anterior se haya completado. Si alguna de las funciones pasa un error a su devolución de llamada ...

  • parallelLimit (tareas, límite, [devolución de llamada])

Al igual que en paralelo, solo las tareas se ejecutan en paralelo con un máximo de tareas de "límite" que se ejecutan en cualquier momento.

En cuanto a mi comprensión del inglés, cuando dices: "hacer tareas en paralelo" significa hacerlas al mismo tiempo, al mismo tiempo.

¿Cómo puede Async.js ejecutar tareas en paralelo en un solo hilo? Me estoy perdiendo de algo.


En cuanto a mi comprensión del inglés, cuando dices: "hacer tareas en paralelo" significa hacerlas al mismo tiempo, al mismo tiempo.

Correcto. Y "simultáneamente" significa "hay al menos un momento en el tiempo cuando dos o más tareas ya están comenzadas, pero aún no han finalizado".

¿Cómo puede Async.js ejecutar tareas en paralelo en un solo hilo? Me estoy perdiendo de algo.

Cuando alguna tarea se detiene por algún motivo (es decir, IO), async.js ejecuta otra tarea y continúa primero una más tarde.


Async.Parallel está bien documentado aquí: https://github.com/caolan/async#parallel

Async.Parallel trata sobre el inicio de tareas de E / S en paralelo, no sobre la ejecución paralela del código. Si sus tareas no usan temporizadores ni realizan ninguna E / S, en realidad se ejecutarán en serie. Cualquier sección de configuración síncrona para cada tarea ocurrirá una después de la otra. JavaScript sigue siendo de un solo subproceso.


Las funciones no se ejecutan simultáneamente, pero cuando la primera función se transfiere a una tarea asíncrona (por ejemplo, setTimeout, red, ...), la segunda comenzará, incluso si la primera función no ha llamado a la devolución de llamada proporcionada.

En cuanto al número de tareas paralelas: eso depende de lo que elija.


Tus dudas tienen mucho sentido. Han pasado algunos años desde que hizo esta pregunta, pero creo que vale la pena agregar algunas ideas a las respuestas existentes.

Ejecute una serie de funciones en paralelo, sin esperar hasta que la función anterior se haya completado. Si alguna de las funciones pasa un error a su devolución de llamada ...

Esta oración no es del todo correcta. De hecho, espera a que se complete cada función porque es imposible no hacerlo en JavaScript. Tanto las llamadas a funciones como los retornos de funciones son sincrónicos y de bloqueo. Entonces, cuando llama a cualquier función, tiene que esperar a que vuelva. Lo que no tiene que esperar es la llamada de la devolución de llamada que se pasó a esa función.

Alegoría

Hace un tiempo, escribí una historia corta para demostrar ese mismo concepto:

Para citar una parte de esto:

"Así que dije: ''Espera un momento, ¿me dices que un pastel tarda tres horas y media y que cuatro pasteles toman solo media hora más de uno? ¡No tiene ningún sentido! Pensé que debía estar bromeando, así que comencé a reír ".
"¿Pero ella no estaba bromeando?"
"No, ella me miró y dijo: ''Tiene mucho sentido. Esta vez es mayormente esperando. Y puedo esperar muchas cosas a la vez bien ". Dejé de reír y comencé a pensar. Finalmente comenzó a llegar a mí. Hacer cuatro almohadas al mismo tiempo no le hizo ninguna compra, tal vez fue más fácil de organizar, pero de nuevo, tal vez no. Pero esta vez fue algo diferente. Pero realmente no sabía cómo usar ese conocimiento ".

Teoría

Creo que es importante destacar que en los bucles de eventos de un solo subproceso nunca se puede hacer más de una cosa a la vez. Pero puedes esperar muchas cosas a la vez bien. Y esto es lo que sucede aquí.

La función paralela del módulo Async llama a cada una de las funciones una por una, pero cada función debe regresar antes de poder llamar a la siguiente, no hay forma de evitarla. La magia aquí es que la función realmente no hace su trabajo antes de que vuelva: simplemente programa alguna tarea, registra un detector de eventos, pasa alguna devolución de llamada a otro lugar, agrega un controlador de resolución a alguna promesa, etc.

Luego, cuando la tarea programada finaliza, se ejecuta algún manejador previamente registrado por esa función, esto a su vez ejecuta la devolución de llamada que originalmente pasó el módulo Async y el módulo Async sabe que esta función ha finalizado, esta vez no solo en un sentido que regresó, pero también que finalmente se llamó la devolución de llamada que se le pasó.

Ejemplos

Entonces, por ejemplo, digamos que tiene 3 funciones que descargan 3 URL diferentes: getA() , getB() y getC() .

Escribiremos un simulacro del módulo Solicitud para simular las solicitudes y algunas demoras:

function mockRequest(url, cb) { const delays = { A: 4000, B: 2000, C: 1000 }; setTimeout(() => { cb(null, {}, ''Response '' + url); }, delays[url]); };

Ahora las 3 funciones que son en su mayoría iguales, con el registro detallado:

function getA(cb) { console.log(''getA called''); const url = ''A''; console.log(''getA runs request''); mockRequest(url, (err, res, body) => { console.log(''getA calling callback''); cb(err, body); }); console.log(''getA request returned''); console.log(''getA returns''); } function getB(cb) { console.log(''getB called''); const url = ''B''; console.log(''getB runs request''); mockRequest(url, (err, res, body) => { console.log(''getB calling callback''); cb(err, body); }); console.log(''getB request returned''); console.log(''getB returns''); } function getC(cb) { console.log(''getC called''); const url = ''C''; console.log(''getC runs request''); mockRequest(url, (err, res, body) => { console.log(''getC calling callback''); cb(err, body); }); console.log(''getC request returned''); console.log(''getC returns''); }

Y finalmente los llamamos a todos con la función async.parallel :

async.parallel([getA, getB, getC], (err, results) => { console.log(''async.parallel callback called''); if (err) { console.log(''async.parallel error:'', err); } else { console.log(''async.parallel results:'', JSON.stringify(results)); } });

Lo que se muestra de inmediato es esto:

getA called getA runs request getA request returned getA returns getB called getB runs request getB request returned getB returns getC called getC runs request getC request returned getC returns

Como puede ver, todo esto es secuencial: las funciones se llaman una por una y la siguiente no se llama antes de que vuelva la anterior. Entonces vemos esto con algunas demoras:

getC calling callback getB calling callback getA calling callback async.parallel callback called async.parallel results: ["Response A","Response B","Response C"]

Entonces getC terminó primero, luego getB y getC , y luego tan pronto como el último finaliza, el async.parallel llama a nuestra devolución de llamada con todas las respuestas combinadas y en el orden correcto, en el orden en que la función fue ordenada por nosotros, no en el orden en que esas solicitudes terminaron.

También podemos ver que el programa finaliza después de 4.071 segundos, que es aproximadamente el tiempo que tardó la solicitud más larga, por lo que vemos que las solicitudes estaban todas en progreso al mismo tiempo.

Ahora, async.parallelLimit con async.parallelLimit con el límite de 2 tareas paralelas como máximo:

async.parallelLimit([getA, getB, getC], 2, (err, results) => { console.log(''async.parallel callback called''); if (err) { console.log(''async.parallel error:'', err); } else { console.log(''async.parallel results:'', JSON.stringify(results)); } });

Ahora es un poco diferente. Lo que vemos inmediatamente es:

getA called getA runs request getA request returned getA returns getB called getB runs request getB request returned getB returns

Entonces getA y getB se llamaron y se devolvieron, pero getC no se llamó getC . Luego, después de un poco de retraso, vemos:

getB calling callback getC called getC runs request getC request returned getC returns

que muestra que tan pronto como getB llama a la devolución de llamada, el módulo Async ya no tiene 2 tareas en progreso, sino solo 1 y puede iniciar otra, que es getC , y lo hace de inmediato.

Luego, con otras demoras, vemos:

getC calling callback getA calling callback async.parallel callback called async.parallel results: ["Response A","Response B","Response C"]

que finaliza todo el proceso como en el ejemplo async.parallel . Esta vez, todo el proceso también tomó aproximadamente 4 segundos porque la llamada demorada de getC no hizo ninguna diferencia, aún así logró finalizar antes de que el primer llamado getA terminara.

Pero si cambiamos los retrasos a aquellos:

const delays = { A: 4000, B: 2000, C: 3000 };

entonces la situación es diferente. Ahora async.parrallel toma 4 segundos pero async.parallelLimit con el límite de 2 toma 5 segundos y el orden es ligeramente diferente.

Sin límite:

$ time node example.js getA called getA runs request getA request returned getA returns getB called getB runs request getB request returned getB returns getC called getC runs request getC request returned getC returns getB calling callback getC calling callback getA calling callback async.parallel callback called async.parallel results: ["Response A","Response B","Response C"] real 0m4.075s user 0m0.070s sys 0m0.009s

Con un límite de 2:

$ time node example.js getA called getA runs request getA request returned getA returns getB called getB runs request getB request returned getB returns getB calling callback getC called getC runs request getC request returned getC returns getA calling callback getC calling callback async.parallel callback called async.parallel results: ["Response A","Response B","Response C"] real 0m5.075s user 0m0.057s sys 0m0.018s

Resumen

Creo que lo más importante para recordar, no importa si utilizas devoluciones de llamada como en este caso, o promesas o async / await, es que en los bucles de eventos de un solo subproceso solo puedes hacer una cosa a la vez, pero puedes esperar a muchos cosas al mismo tiempo.