2008-12-04 14 views
13

Tenga en cuenta el siguiente fork()/SIGCHLD pseudocódigo.Evitar una condición de carrera de horquilla()/SIGCHLD

// main program excerpt 
    for (;;) { 
     if (is_time_to_make_babies) { 

     pid = fork(); 
     if (pid == -1) { 
      /* fail */ 
     } else if (pid == 0) { 
      /* child stuff */ 
      print "child started" 
      exit 
     } else { 
      /* parent stuff */ 
      print "parent forked new child ", pid 
      children.add(pid); 
     } 

     } 
    } 

    // SIGCHLD handler 
    sigchld_handler(signo) { 
    while ((pid = wait(status, WNOHANG)) > 0) { 
     print "parent caught SIGCHLD from ", pid 
     children.remove(pid); 
    } 
    } 

En el ejemplo anterior, hay una condición de carrera. Es posible que "/* child stuff */" finalice antes de que "/* parent stuff */" comience, lo que puede dar como resultado que se agregue un pid de un niño a la lista de elementos secundarios después de salir y que nunca se elimine. Cuando llegue el momento de que la aplicación se cierre, el padre esperará sin parar para que el niño ya terminado termine.

Una solución que se me ocurre para contrarrestar esto es tener dos listas: started_children y finished_children. Agregaría a started_children en el mismo lugar que estoy agregando al children ahora. Pero en el controlador de señal, en lugar de eliminar de children, me agregar a finished_children. Cuando la aplicación se cierra, el padre simplemente puede esperar hasta que la diferencia entre started_children y finished_children sea cero.

Otra solución posible en la que puedo pensar es usar la memoria compartida, p. compartir la lista de hijos de los padres y dejar a los niños .add y .remove ellos mismos? Pero no sé mucho sobre esto.

EDITAR: Otra solución posible, que fue lo primero que me vino a la mente, es simplemente agregar un sleep(1) al comienzo de /* child stuff */ pero me huele raro, por eso lo dejé fuera. Tampoco estoy seguro de que sea una solución al 100%.

Entonces, ¿cómo corregirías esta condición de carrera? Y si hay un patrón recomendado bien establecido para esto, ¡házmelo saber!

Gracias.

+0

¿Es solo yo o el controlador de señales no es seguro? ¿Cómo podría children.remove() posiblemente implementarse para no explotar cuando un nuevo SIGCHLD lo interrumpe en el medio? –

Respuesta

15

La solución más simple sería bloquear la señal SIGCHLD antes de fork() con sigprocmask() y desbloquearla en el código principal después de haber procesado el pid.

Si el niño murió, se llamará al controlador de señal para SIGCHLD después de desbloquear la señal. Es un concepto de sección crítica: en su caso, la sección crítica comienza antes de fork() y finaliza después de children.add().

+0

Me gusta esta solución. Lamentablemente estoy haciendo esto en PHP y no hay sigprocmask() en un lanzamiento todavía :(Está en CVS, así que es solo cuestión de tiempo, supongo. Gracias por la información. Tal vez debería usar un lenguaje diferente para esto. proyecto - no setproctitle() - por igual en PHP, o eso parece. –

-1

Además de los "hijos" existentes, agregue una nueva estructura de datos "muertes prematuras". Esto mantendrá limpio el contenido de los niños.

// main program excerpt 
    for (;;) { 
     if (is_time_to_make_babies) { 

     pid = fork(); 
     if (pid == -1) { 
      /* fail */ 
     } else if (pid == 0) { 
      /* child stuff */ 
      print "child started" 
      exit 
     } else { 
      /* parent stuff */ 
      print "parent forked new child ", pid 
      if (!earlyDeaths.contains(pid)) { 
       children.add(pid); 
      } else { 
       earlyDeaths.remove(pid); 
      } 
     } 

     } 
    } 

    // SIGCHLD handler 
    sigchld_handler(signo) { 
    while ((pid = wait(status, WNOHANG)) > 0) { 
     print "parent caught SIGCHLD from ", pid 
     if (children.contains(pid)) { 
      children.remove(pid); 
     } else { 
      earlyDeaths.add(pid); 
     } 
    } 
    } 

EDIT: esto se puede simplificar si el proceso es de un solo subproceso - earlyDeaths no tiene por qué ser un contenedor, sólo tiene que contener un pid.

+0

Realmente no resuelve la condición de carrera - el niño puede morir mientras el padre está entre 'if (! EarlyDeaths.contains (pid))' y 'children.add (pid)' – qrdl

0

Si no puede usar el fragmento crítico, tal vez un simple contador pueda hacer este trabajo. +1 cuando agrega, -1 cuando se elimina, no importa cuál suceda primero, eventualmente puede obtener cero cuando todo está hecho.

-1

¿Quizás un algoritmo optimista? Pruebe children.remove (pid), y si falla, continúe con vida.

¿O comprueba que pid está en los niños antes de intentar eliminarlo?

Cuestiones relacionadas