Fork de Procesos en PHP
¿Necesitamos ejecutar más de una acción al mismo tiempo en PHP?
Como en otros lenguajes, PHP dispone de un par de herramientas para crear y manejar procesos duros, especificamente, la extensión Process Control (PCNTL) solo disponible en sistemas like Unix.
Cuando un proceso realiza una llamada a fork, el sistema crea otro proceso nuevo, exactamente igual al padre (en el punto de la bifurcación fork) pero con un PID distinto.
Dichos procesos son independiente y no tienen una comunicación directa, si deseamos comunicarlos entre si, debemos implementar algún tipo de comunicación entre procesos, como el envío de señales, comunicación por sockets unix o tcp, memoría compartida, etc.
Hay muchas tareas que obtienen un mayor rendimiento al realizarlas con procesos concurrente versus a un proceso, como la descarga de archivos, la busqueda de archivos, ping a un rango de red, etc.
Para llamar a la función fork del sistema desde PHP, tenemos que utilizar la función pcntl_fork, ella devuelve el pid del nuevo proceso al padre, por lo tanto, con un if podemos determinar ¿quién es el padre o el hijo?
<?php $father_pid = getmypid(); $pid = pcntl_fork(); if ($pid == -1) { die('could not fork'); } else if ($pid) { print "I'm the Father mi PID is ".$father_pid." and my children is ".$pid."\n"; pcntl_wait($status); //Protect against Zombie children } else { print "I'm the Children, my PID is ".getmypid()." and the PID of my Father is ".$father_pid."\n"; }
Podemos observar que tanto el padre como el hijo tiene PID distintos.
Descarga de Imágenes de un Documento Issuu
El siguiente script, es un pequeño ejemplo de la descarga de las imágenes de un documento issuu, utilizando uno o más procesos.
<? /** * dIssu * * This script allows you to download the images from the books of issuu (fork version) * * @package pserver * @subpackage pserver.class * @author Pedro Vargas (info@deerme.org) http://deerme.org * @version 0.1 */ function issuu_download( $id , $pages ) { $url = "http://image.issuu.com/%s/jpg/page_%s.jpg"; if( is_numeric( $pages ) ) { $t = $pages; $pages = array(); $pages[] = $t; } foreach( $pages as $k => $page ) { $c=0; $name = sprintf("%03s",$page); while( !copy( sprintf( $url , $id , $page ) , "./". $name .".jpg" ) AND $c < 10 ) { $c++; } echo "nr$page"; } } if ( !$argv[1] ) $id = "100112181114-863a0894edd54624b859f4d370537f75"; else $id = $argv[1]; if ( !$argv[2] ) $ini = 1; else $ini = (int)$argv[2]; if ( !$argv[3] ) $fin = 10; else $fin = (int)$argv[3]; if ( !$argv[4] ) { print "nProcess Fork Versionnr"; $fv = 1; } else { print "nMono Process Versionnr"; $fv = 0; } $maxchildren = 4; for( $i=$ini;$i<=$fin;$i++ ) { $npages[] = $i; if ( (count($npages) % (($fin-$ini)/$maxchildren ) == 0) OR $i == $fin ) { // Fork Version if ( $fv ) { $pid = pcntl_fork(); if($pid == -1) { die('could not fork'); } else if ($pid) { // I am the Father $pids[] = $pid; unset( $npages ); } else { // I am the Son issuu_download( $id , $npages ); die(); } } // One process else { issuu_download( $id , $npages ); unset( $npages ); } } } foreach($pids as $pid) { pcntl_waitpid($pid, $status); } ?>
php dissuu.php iddoc pini pfin [0/1]
Dispone de 4 parametros
1.- iddoc es el id de un documento issuu (lo podemos ver en el fuente de un documento issuu).
2.- pini, es la página de inicio a descargar (generalmente 1).
3.- pfin, es la página final a descargar.
4.- 0/1, este parametro es opcional, permite correr el script utilizando uno o más procesos concurrentes.
Comparación
Vamos a descargar dos documentos, cada uno utilizando un solo proceso o varios procesos concurrentes, el tiempo de ejecución lo vamos a medir con el comando time.
copiapo:/tmp# time php downissuuF.php 100112181114-863a0894edd54624b859f4d370537f75 1 16 Process Fork Version real 0m26.533s user 0m0.020s sys 0m0.104s copiapo:/tmp# time php downissuuF.php 100112181114-863a0894edd54624b859f4d370537f75 1 16 false Mono Process Version real 0m42.070s user 0m0.000s sys 0m0.064s copiapo:/tmp# time php downissuuF.php 090829152404-eb2777e1d8614ec8918c0ee0e3b6a80a 1 66 Process Fork Version real 1m20.116s user 0m0.016s sys 0m0.316s copiapo:/tmp# time php downissuuF.php 090829152404-eb2777e1d8614ec8918c0ee0e3b6a80a 1 66 false Mono Process Version real 1m58.648s user 0m0.008s sys 0m0.092s copiapo:/tmp#
Podemos notar (en los dos ejemplos) que al utilizar más procesos, la tarea total, fue más rapida que al ejecutar un solo proceso. Ambas ejecuciones fueron en condiciones muy similares (el mismo servidor, el mismo ancho de banda, etc.) y realizaron la misma tarea (descargar varios archivos), finalmente la diferencia de tiempo, en gran medida, depende de todas las pausas que realiza el sistema operativo en cada proceso, ya que accedemos a un recurso de red (I/O), el bloqueo entre procesos, el tiempo que de ejecución real en la CPU de cada proceso, etc.