Más concurrencia de procesos en PHP con tuberías (pipes). ¡Vamos a tirar más dados!

En el post anterior de esta categoría, Concurrencia de procesos con PHP, el problema de los dados implementado con tuberías (pipes), hemos copypasteado un ejercicio introductorio a la concurrencia de procesos en PHP.

Full house from dice

Recordemos que en aquel ejercicio un proceso se replicaba, el proceso hijo tiraba un dado cuyo resultado devolvía al padre, y, finalmente, el proceso padre recogía el resultado y lo imprimía en la pantalla. Hoy complicamos un poco más aquel ejercicio introductorio.

Este post presenta la versión donde el proceso padre tira diez dados (ya verás que ese número diez es arbitrario, el proceso se replica en un bucle tantas veces como tú quieras) y a continuación hace la suma de todas las tiradas.

En otras palabras, hay un proceso padre que se replica diez veces, luego, los diez procesos hijo tiran un dado cada uno por su cuenta, independientemente de todo lo demás, y, finalmente, el proceso padre recoge el resultado de cada una de las tiradas de sus hijos para hacer la suma de todas ellas.

Para simplificar las cosas creamos dos archivos. El primero, muy sencillito, se llama dado.php. dado.php tira un dado, o sea, calcula un número aleatorio comprendido entre 1 y 6.

dado.php:

#!/usr/bin/php5
<?php
echo rand(1,6);

Puedes asegurarte de que este programa funciona ejecutándolo en la consola:

php dado.php
6

Al segundo archivo lo vamos a llamar diez-tiradas.php:

#!/usr/bin/php5
<?php
$sumRolls = 0;

for ($i = 0; $i < 10; ++$i) {
    $pipe = "/tmp/pipe-$i";
    $mode = 0600;

    if (!file_exists($pipe)) {
        posix_mkfifo($pipe, $mode);
    }

    $pid = pcntl_fork();

    switch ($pid) {
        case -1:
            die("Error al utilizar la función fork\n");
            break;

        case 0:
            $resultRoll = exec('/home/jordi/./dado.php');
            $f = fopen($pipe, 'w');
            fwrite($f, $resultRoll);
            exit;

        default:
            $f = fopen($pipe, 'r');
            $resultRoll = fgets($f);
            unlink($pipe);
            printf("La tirada de mi hijo con PID $pid es $resultRoll.\n");
            $sumRolls = $sumRolls + (int) $resultRoll;
            break;
    }
}

for ($i = 0; $i < 10; ++$i) {
    pcntl_wait($status);
}

printf("El resultado de la suma de las 10 tiradas es $sumRolls.\n");

exit;

Este archivo tiene toda la chicha programática, es el quid de la cuestión que tendrías que ser capaz de hallar tú mism@ en algún momento de tu proceso de aprendizaje para aprobar tu examen sobre la gestión de procesos en sistemas de tipo UNIX. ¿Cómo lo ves?

En síntesis, la dificultad reside aquí:

$pipe = "/tmp/pipe-$i";
$mode = 0600;

if (!file_exists($pipe)) {
    posix_mkfifo($pipe,$mode);
}

$pid = pcntl_fork();

La creación de la tubería (pipe) de comunicación debe ocurrir justo antes de que el proceso padre se replique. De este modo conseguimos un esquema de tipo 1 a 10, digamos, donde el padre se comunica con cada uno de sus hijos a través de la tubería que comparte exclusivamente con cada uno de ellos.

diez-tiradas.php es también un PHP CLI; por consiguiente, lo ejecutamos desde la consola:

php diez-tiradas.php
La tirada de mi hijo con PID 4291 es 3.
La tirada de mi hijo con PID 4295 es 4.
La tirada de mi hijo con PID 4299 es 6.
La tirada de mi hijo con PID 4303 es 3.
La tirada de mi hijo con PID 4307 es 4.
La tirada de mi hijo con PID 4311 es 2.
La tirada de mi hijo con PID 4315 es 5.
La tirada de mi hijo con PID 4319 es 1.
La tirada de mi hijo con PID 4323 es 4.
La tirada de mi hijo con PID 4327 es 1.
El resultado de la suma de las 10 tiradas es 33.