Introducción al event Loops en PHP

Introducción al event Loops en PHP

Los desarrolladores de PHP siempre están esperando algo. A veces esperan peticiones a servicios remotos. Otras esperan a las bases de datos para devolver filas de una consulta compleja. ¿Sería posible hacer otra cosa durante el tiempo de espera?

Si ya has escrito algo de JS, probablemente estés familiarizado con las devoluciones de llamada y eventos DOM. Y aunque tenemos devoluciones de llamada en PHP, no trabajan de la misma forma. Eso es gracias a una característica llamada event loops.

Vamos a ver cómo funciona el event loops, y cómo podemos utilizarlo en PHP.

Vamos a ver algunas bibliotecas PHP interesantes. Algunos consideran que esto no es aún lo suficientemente estable como para utilizarlo en producción. El propósito de este artículo es poner de relieve lo que es posible en PHP.

Cuando entramos en espera

Para comprender los event loops, vamos a ver cómo funcionan en el navegador. Echa un vistazo a este ejemplo:

function fitToScreen(selector)
{     var element = document.querySelector(selector); 
 var width = element.offsetWidth; 
 var height = element.offsetHeight; 
 var top = "-" + (height / 2) + "px"; 
 var left = "-" + (width / 2) + "px"; 
 var ratio = getRatio(width, height); 
 setStyles(element, { 
 "position": "absolute", 
 "left": "50%", 
 "top": "50%", 
 "margin": top + " 0 0 " + left, 
 "transform": "scale(" + ratio + ", " + ratio + ")" 
 }); 
} 
 function getRatio(width, height) { 
 return Math.min( 
 document.body.offsetWidth / width, 
 document.body.offsetHeight / height 
 ); 
} 
 function setStyles(element, styles) { 
 for (var key in styles) { 
 if (element.style.hasOwnProperty(key)) { 
 element.style[key] = styles[key]; 
} } } fitToScreen(".welcome-screen");

Este código no requiere librerías adicionales. Funciona en cualquier navegador que soporte transformaciones de escala en CSS. Una versión reciente de Chrome es suficiente. Sólo asegúrate de que el selector CSS coincide con un elemento en el documento.

Estas funciones utilizan un selector CSS y centran y escalan el elemento para ajustarse a la pantalla. ¿Qué pasa si ponemos un error dentro del loop for? Veríamos algo como esto.


Imaginanet

Llamamos a esa lista de funciones seguimiento de una lista (stack). Así es como se ven las cosas en el interior de las listas que utilizan los navegadores. Estos manejan el código en pasos.


Imaginanet

Esta es como la forma en que PHP utiliza una lista para archivar el contexto. Los navegadores van un paso más allá y ofrecen WebAPIs para cosas como eventos DOM y devoluciones de llamada de Ajax. En su estado natural, JavaScript es tan asíncrono como PHP. Es decir: mientras que parece que pueden realizar varias cosas a la vez, son tratados individualmente. Sólo pueden hacer una cosa a la vez.

Con las WebAPIs de navegador (cosas como setTimeout y addEventListener) podemos descargar el trabajo paralelamente a diferentes hilos. Cuando estos eventos ocurren, los navegadores añaden devoluciones de llamada a una cola de devolución de llamada. Cuando la lista es next empty, los navegadores buscan recoger las devoluciones de llamada desde la cola de devolución de llamada y ejecutarlas.

Este proceso de limpieza de la lista, y luego la cola de devolución de llamada, es el evento loop.

Sin event loop

En JS, podemos ejecutar el siguiente código:

setTimeout(function() {     
console.log("inside the timeout");
 }, 1);  
console.log("outside the timeout");

Cuando ejecutamos este código, vemos outside the timeout y luego inside the timeout en la consola. La función setTimeout es parte de los WebAPIs que los navegadores aportan para trabajar. Cuando ha pasado 1 milisegundo, añaden la devolución de llamada a la cola de devolución de llamada.

El segundo console.log se completa antes de que uno del interior del setTimeout comience. No hay nada parecido a setTimeout en PHP estándar, pero sí lo tenemos que probar y simular:

function setTimeout(callable $callback, $delay) {
$now = microtime(true);
>while (true) {
if (microtime(true) - $now > $delay) {
$callback();
return;
}
}
}  
setTimeout(function() {
print "inside the timeout";
}, 1);  
print "outside the timeout";

Cuando utilizamos esto, vemos inside the timeout y luego outside the timeout. Esto se debe a que tenemos que utilizar un bucle infinito en el interior de nuestra función setTimeout para ejecutar la devolución de llamada después de un retraso.

Puede ser tentador mover el loop while fuera de setTimeout y envolver todo nuestro código en él. Eso podría hacer que nuestro código se sienta menos bloqueado, pero en algún momento que siempre vas a quedar bloqueado por ese bucle. En algún momento veremos cómo no es posible hacer más que una cosa en un solo hilo a la vez.

Si bien no hay nada como setTimeout en PHP estándar, hay algunas formas para implementar código sin bloqueo junto a event loops. Podemos utilizar funciones como stream_select para crear redes IO sin bloqueo. Podemos utilizar las extensiones de C como EIO para crear código de sistema de archivos sin bloqueo. Echemos un vistazo a las bibliotecas construidas sobre estos métodos.

Icicle

 Icicle es una librería de componentes realizada teniendo en cuenta el event loop. Veamos un ejemplo sencillo:

use IcicleLoop;  
Looptimer(0.1, function() {
print "inside timer"; 
});
print "outside timer";
Looprun();

Esto es con icicleio/icicle  versión 0.8. 0.

La aplicación event loop de Icicle es muy útil. Tiene muchas otras características interesantes; como A + promises, socket, y ??las implementaciones de servidor.

Icicle también utiliza generadores como co-rutinas. Generadores y co-rutinas son un tema diferente, pero permiten código como este:

use IcicleCoroutine; 
use IcicleDnsResolverResolver; 
use IcicleLoop;  
$coroutine = Coroutinecreate(function ($query, $timeout = 1) {
$resolver = new Resolver();
$ips = (yield $resolver->resolve(
$query, ["timeout" => $timeout]
));
foreach ($ips as $ip) {
print "ip: {$ip}n";
}
}, "imaginanet.com");
Looprun();

Esto es con icicleio/dns versión 0.5. 0.

Los generadores hacen que sea más fácil escribir código asíncrono de una manera que se asemeja al código síncrono. Cuando se combina con Promises y event loop, derivan en un gran código de no-bloqueo como ese.

ReactPHP

ReactPHP tiene una aplicación event loop similar, pero sin el generador:

$loop = ReactEventLoopFactory::create();
$loop->addTimer(0.1, function () {
print "inside timer";
});
print "outside timer";
$loop->run();

Esto es con react/event-loop versión 0.4. 1.

ReactPHP es más antiguo que Icicle, y tiene una gama más amplia de componentes. Icicle tiene un camino por recorrer antes de que pueda pelear con todas las funcionalidades de ReactPHP.

Conclusión

Es difícil salir de la mentalidad de un único subproceso a la que estamos acostumbrados.

La comunidad PHP tiene que tomar conciencia de este tipo de arquitectura. Debe aprender y experimentar con la ejecución asíncrona y paralela. ¿Cómo puedo usar la mayor cantidad de recursos del sistema, de manera eficiente? Es una pregunta fácil de responder con PHP.

Comentarios

Sin comentarios
Ha habido un error en el envío
Comentario enviado. Será revisado por la moderación antes de ser publicado.

Deja tu comentario

Tu nombre:
Tu email:
Tu comentario: