May 262014
 
Artículo Perl

Un script perl normalmente comienza leyendo información de una serie de fuentes: archivos del disco, bases de datos, conexiones de red, teclado… Después procesa la información, y por último entrega el resultado escribiéndolo en un archivo, guardándolo en una base de datos, enviándolo por la red, presentándolo en pantalla,….

En ocasiones, esta tarea se puede dividir en subtareas, cada una de las cuales sigue el mismo esquema de leer/procesar/escribir la información.

En este caso, se puede obtener una mejora sustancial del rendimiento del script si estas tareas se ejecutan en paralelo, porque mientras unas subtareas pueden estar esperando recibir información de la red, otras pueden estar utilizando la CPU para realizar el proceso de los datos, y otras pueden estar esperando que finalice una operación de entrada/salida al disco.

En este artículo explicamos como programar un script perl para que ejecute varios subprocesos simultáneos y aproveche así al máximo los recursos disponibles.

La función fork()

La funcionalidad que buscamos se obtiene mediante el uso de la función fork(). Esta función crea un subproceso que se ejecuta de manera independiente, en paralelo con el programa principal. El subproceso hereda todo el entorno del programa que lo ha creado, incluyendo las variables que han sido definidas y sus valores, los manejadores de ficheros, conexiones a bases de datos, etc…

Ejemplo:

La llamada a fork devuelve al proceso padre un identificador del proceso hijo, y devuelve al proceso hijo el valor cero. Esto permite dividir, mediante la sentencia “if ($pid)” el código que ejecuta el padre del que ejecuta el hijo.

Este mecanismo se puede utlizar para que un mismo proceso padre cree múltiples hijos, cada uno de los cuales estará identificado por un valor distinto de $pid.

Por último, el proceso padre puede esperar a que termine la ejecución de todos sus hijos mediante la llamada a “wait()”.

Esperando a que termine el hijo

Normalmente, el proceso padre debe esperar hasta que todos los procesos hijo finalizan su ejecución, antes de finalizar él mismo. Esto se hace mediante una llamada a wait(), como se puede ver en el ejemplo anterior. wait() suspende la ejecución del proceso padre hasta que uno de los procesos hijo finaliza, y devuelve el PID del hijo que ha finalizado. Cuando ya no quedan procesos hijo en ejecución, wait() devuelve -1.

También existe una función waitpid() que permite al proceso padre quedar a la espera de que finalice un proceso hijo en concreto. La función waitpid() recibe como primer argumento el ID del proceso hijo a esperar, y como segundo argumento o bien un cero, para suspender la ejecución del proceso padre, o bien la constante WNOHANG para permitir que el padre continúe ejecutándose.

En lugar del PID de un proceso hijo en concreto, se puede pasar el valor -1 como primer argumento de waitpid(). En este caso, waitpid() queda a la espera de que finalice cualquiera de los procesos hijo en ejecución, y devuelve el PID del proceso hijo que haya finalizado. Es decir, que una llamada a waitpid(-1,0) es equivalente a una llamada a wait().

En la llamada a waitpid, se puede utilizar también como segundo argumento la constante WNOHANG. En este caso la llamada no suspende la ejecución del proceso padre, sino que devuelve inmediatamente un valor que puede ser:

  • cero si no hay ningún hijo que haya finalizado desde la última llamada a waitpid
  • el PID de uno de los procesos hijo que ha finalizado. Si hay más de un proceso que ha finalizado, sucesivas llamadas a waitpid van devolviendo los PID de los mismos.
  • -1 si no quedan procesos hijo en ejecución, y las sucesivas llamadas a waitpid han devuelto ya todos los PIDs de los hijos que han finalizado.

Paso de datos entre el proceso padre y el proceso hijo

fork() crea una copia del entorno del proceso padre, de manera que las variables que estaban definidas en el proceso padre también están definidas en el proceso hijo, con los mismos valores. Pero las modificaciones que realice el proceso hijo a los valores de una variable no son visibles por el proceso padre.

Para poder intercambiar datos entre el proceso padre y el proceso hijo, se puede establecer un canal de comunicación mediante el uso de “pipes”:

Cuando la función open() es llamada con el argumento “-|”, funciona igual que la función fork(), creando un subproceso. Pero además, devuelve un manejador de entrada/salida asociado a la salida estándar (STDOUT) del proceso hijo. De este modo, lo que escriba el proceso hijo por la salida estándar puede ser leído por el proceso padre.

Ejemplo 1. Comunicación en el sentido hijo -> padre:

De la misma manera, se puede utilizar la función open() con el argumento “|-“. En este caso, se abre un canal de comunicación unidireccional en el sentido padre -> hijo.

Ejemplo 2. Comunicación en el sentido padre -> hijo:

Comunicación bidireccional con pipes

Si necesitamos establecer una comunicación en ambos sentidos, podemos crear explícitamente dos canales de comunicación unidireccionales con la función pipe(), y crear el subproceso con la función fork():

 Publicado por en 2:04 pm

 Deja un comentario

(requerido)

(requerido)