Multiprocesos con fork en PHP (y II): Procesos zombi

Los zombies molan en las películas y en las series, pero encontrárselos en la vida real o en la tabla de procesos de tu sistema ya no mola tanto.

Este artículo es la continuación de Multiprocesos con fork en PHP (y I).

Antes de continuar debo decir que apenas he usado fork en PHP. Todo lo que comparto es fruto de una pequeña investigación, mis recuerdos de mi época como programador de C y mis experimentos a la hora de escribir estos artículos.


(Una cosilla antes de que sigas leyendo… estoy escribiendo un libro muy divertido sobre buenas prácticas en PHP titulado «Aliens contra Elefantes»).


¿Qué es un proceso zombi?

Cuando un proceso hijo finaliza lanza un aviso al padre. El proceso padre puede terminar cuando ya no se le necesita más o puede esperar a que termine su proceso hijo (o hijos) haciendo uso de la función:

pcntl_wait($status);

Si el padre termina antes que el proceso hijo éste se queda huérfano. En ese caso es adoptado por el proceso principal del sistema llamado init (el primer proceso del sistema). Esto no es malo ya que init se encarga de hacer limpieza de procesos zombi que pueda haber.

Si el padre espera a que termine su hijo usando pcntl_wait() todo va bien, esto tampoco es malo.

Pero si el hijo termina antes que el padre y éste no hace la llamada a pcntl_wait() entonces el hijo se queda en estado zombi.

¿Es grave tener un proceso zombi?

Hasta donde yo se, los procesos zombi no ocupan memoria ni consumen tiempo de procesamiento. Sin embargo siguen reteniendo su PID en la tabla de procesos. Dado que la tabla de procesos no es infinita se puede producir un problema si tenemos muchos procesos zombis.

Ejemplo de un proceso zombi

Este sencillo programa debería crear un proceso zombi porque el hijo termina antes que el padre pero éste no hace la consabida llamada a pcntl_wait():

<?php

$pid = pcntl_fork();
if ($pid == -1) {
    die('Ha fallado el fork');
}

if ($pid) {
    echo "[Padre] Soy el padre.\n";
    sleep(60);
}
else {
    echo "[Hijo] Soy el hijo.\n";
    sleep(10);
}

Si abrimos una consola y ejecutamos el comando ps aux mientras ambos procesos están en marcha veremos algo parecido a ésto (el script tiene el nombre fork-zombie.php):

$ ps aux | grep php
gorka 1056095 0.1 0.0 95200 26092 pts/0 S+ 17:58 0:00 php fork-zombie.php
gorka 1056096 0.0 0.0 95200 5288 pts/0 S+ 17:58 0:00 php fork-zombie.php

La primera línea es el proceso padre y la segunda el hijo.

Si volvemos a ejecutar el comando cuando el hijo ha terminado:

gorka 1056095 0.0 0.0 95200 26092 pts/0 S+ 17:58 0:00 php fork-zombie.php
gorka 1056096 0.0 0.0 0 0 pts/0 Z+ 17:58 0:00 [php] <defunct>

Aquí tenemos ya el proceso hijo como zombi.

Y lo que me descoloca es que, cuando el padre termina el hijo deja de aparecer como zombi. Vamos, que desaparece del todo. Asumo que se debe a que se detecta como zombi de alguna manera y el sistema lo limpia. Seguiré mi investigación.

La forma correcta

Como ya he comentado, la forma correcta de terminar el proceso padre es haciendo una llamada a pcntl_wait() y asegurarnos de que los hijos no se convierten en zombis:

<?php

$pid = pcntl_fork();
if ($pid == -1) {
    die('Ha fallado el fork');
}

if ($pid) {
    echo "[Padre] Soy el padre.\n";
    sleep(60);
    pcntl_wait($status);
}
else {
    echo "[Hijo] Soy el hijo.\n";
    sleep(10);
}

Deja un comentario