Pregunta

Tengo 3 procesos que necesitan ser sincronizado. Proceso uno hace algo, entonces se despierta y duerme proceso de dos, lo que hace algo, entonces se despierta y duerme proceso de tres, lo que hace algo y se despierta un solo proceso y duerme. todo el bucle se mide el tiempo que correr alrededor de 25 Hz (causada por una sincronización externa en el proceso anterior se dispara proceso de dos en mi aplicación "real"). Yo uso sem_post al gatillo (estela) cada proceso, y sem_timedwait () para esperar a que el gatillo.

Todo esto funciona con éxito durante varias horas. Sin embargo, en algún tiempo aleatorio (normalmente después de entre dos y cuatro horas), uno de los procesos empieza a contar en sem_timedwait (), a pesar de que estoy seguro que el semáforo está siendo activado con sem_post (). Para probar esto incluso utilizar sem_getvalue () inmediatamente después de que el tiempo de espera, y el valor es 1, por lo que el timedwait debería haber sido activado.

Por favor, véase el siguiente código:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>

sem_t trigger_sem1, trigger_sem2, trigger_sem3;

// The main thread process.  Called three times with a different num arg - 1, 2 or 3.
void *thread(void *arg)
{
  int num = (int) arg;
  sem_t *wait, *trigger;
  int val, retval;
  struct timespec ts;
  struct timeval tv;

  switch (num)
    {
      case 1:
        wait = &trigger_sem1;
        trigger = &trigger_sem2;
        break;
      case 2:
        wait = &trigger_sem2;
        trigger = &trigger_sem3;
        break;
      case 3:
        wait = &trigger_sem3;
        trigger = &trigger_sem1;
        break;
    }

  while (1)
    {
      // The first thread delays by 40ms to time the whole loop.  
      // This is an external sync in the real app.
      if (num == 1)   
        usleep(40000);

      // print sem value before we wait.  If this is 1, sem_timedwait() will
      // return immediately, otherwise it will block until sem_post() is called on this sem. 
      sem_getvalue(wait, &val);
      printf("sem%d wait sync sem%d. val before %d\n", num, num, val);

          // get current time and add half a second for timeout.
      gettimeofday(&tv, NULL);
      ts.tv_sec = tv.tv_sec;
      ts.tv_nsec = (tv.tv_usec + 500000);    // add half a second
      if (ts.tv_nsec > 1000000)
        {
          ts.tv_sec++;
          ts.tv_nsec -= 1000000;
        }
      ts.tv_nsec *= 1000;    /* convert to nanosecs */

      retval = sem_timedwait(wait, &ts);
      if (retval == -1)
        {
          // timed out.  Print value of sem now.  This should be 0, otherwise sem_timedwait
          // would have woken before timeout (unless the sem_post happened between the 
          // timeout and this call to sem_getvalue).
          sem_getvalue(wait, &val);
          printf("!!!!!!    sem%d sem_timedwait failed: %s, val now %d\n", 
            num, strerror(errno), val);
        }
      else
        printf("sem%d wakeup.\n", num);

        // get value of semaphore to trigger.  If it's 1, don't post as it has already been 
        // triggered and sem_timedwait on this sem *should* not block.
      sem_getvalue(trigger, &val);
      if (val <= 0)
        {
          printf("sem%d send sync sem%d. val before %d\n", num, (num == 3 ? 1 : num+1), val);
          sem_post(trigger);
        }
      else
        printf("!! sem%d not sending sync, val %d\n", num, val);
    }
}



int main(int argc, char *argv[])
{
  pthread_t t1, t2, t3;

   // create semaphores.  val of sem1 is 1 to trigger straight away and start the whole ball rolling.
  if (sem_init(&trigger_sem1, 0, 1) == -1)
    perror("Error creating trigger_listman semaphore");
  if (sem_init(&trigger_sem2, 0, 0) == -1)
    perror("Error creating trigger_comms semaphore");
  if (sem_init(&trigger_sem3, 0, 0) == -1)
    perror("Error creating trigger_vws semaphore");

  pthread_create(&t1, NULL, thread, (void *) 1);
  pthread_create(&t2, NULL, thread, (void *) 2);
  pthread_create(&t3, NULL, thread, (void *) 3);

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
}

La siguiente salida se imprime cuando el programa se ejecuta correctamente (al inicio y durante un tiempo aleatorio, pero mucho después). El valor de SEM1 es siempre 1 antes Thread1 espera, ya que tiene capacidad para 40 ms, por el cual sem3 momento de su activación, por lo que se despierta de inmediato. Los otros dos hilos esperan hasta que el semáforo se recibe desde el hilo anterior.

[...]
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
sem2 wakeup.
sem2 send sync sem3. val before 0
sem2 wait sync sem2. val before 0
sem3 wakeup.
sem3 send sync sem1. val before 0
sem3 wait sync sem3. val before 0
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
[...]

Sin embargo, después de unas pocas horas, uno de los hilos comienza a tiempo de espera. Puedo ver a partir de la salida que el semáforo está siendo activado, y al imprimir el valor de tiempo de espera después de la, es 1. Así sem_timedwait debería haber despertado mucho antes del tiempo de espera. Nunca esperaría que el valor del semáforo sea 1 después de que el tiempo de espera, excepto para la ocasión muy rara (casi seguro que nunca pero es posible) cuando el gatillo ocurre después de que el tiempo de espera, pero antes de que llame sem_getvalue.

Además, una vez que comienza a fallar, cada sem_timedwait () en ese semáforo también falla de la misma manera. Ver la siguiente salida, que numeré línea hemos:

01  sem3 wait sync sem3. val before 0
02  sem1 wakeup.
03  sem1 send sync sem2. val before 0
04  sem2 wakeup.
05  sem2 send sync sem3. val before 0
06  sem2 wait sync sem2. val before 0
07  sem1 wait sync sem1. val before 0
08  !!!!!!    sem3 sem_timedwait failed: Connection timed out, val now 1
09  sem3 send sync sem1. val before 0
10  sem3 wait sync sem3. val before 1
11  sem3 wakeup.
12  !! sem3 not sending sync, val 1
13  sem3 wait sync sem3. val before 0
14  sem1 wakeup.
[...]

En la línea 1, hilo 3 (que he llamado confusamente sem3 en el printf) espera a sem3 a ser activado. En la línea 5, Thread2 llamadas sem_post para sem3. Sin embargo, la línea 8 muestra sem3 el tiempo de espera, pero el valor del semáforo es 1. thread3 entonces disparadores SEM1 y espera de nuevo (10). Sin embargo, debido a que el valor ya es 1, se despierta de inmediato. No envía SEM1 de nuevo como todo esto ha sucedido antes de que se le dio el control a Thread1, sin embargo se espera entonces de nuevo (val es ahora 0) y estelas SEM1 arriba. Esto ahora se repite para siempre, sem3 siempre el tiempo de espera y demostrando que el valor es 1.

Por lo tanto, mi pregunta es ¿por qué el tiempo de espera sem3, a pesar de que el semáforo se ha disparado y el valor es claramente 1? Yo nunca esperaría para ver la línea 08 en la salida. Si el tiempo de espera (porque, por ejemplo hilo 2 se ha estropeado o está tomando demasiado tiempo), el valor debe ser 0. Y por qué funciona bien para 3 o 4 horas antes de entrar en este estado?

He intentado una prueba similar usando tres programas separados, la comunicación de memoria compartida más, en lugar de tres hilos en el mismo programa. Esto se parece más a mi aplicación en el mundo real. Los resultados y la salida eran los mismos. El problema parece estar en el semáforo (en particular la llamada sem_timedwait) más que nada que ver con pthreads.

I también han tratado retrasos cortos y más largos, así como la eliminación de la demora por completo, con resultados similares a los descritos anteriormente. Sin demora a todos los que a veces puede comenzar a producir el error después de minutos en lugar de horas. Esto hace, por supuesto, significa que el problema puede reproducirse mucho más rápido.

Esto es usando Ubuntu 9.4 con el kernel 2.6.28. El mismo procedimiento ha estado trabajando adecuadamente en RedHat y Fedora, pero ahora estoy tratando de portar a Ubuntu. También he intentado usar Ubuntu 9.10, que no hizo ninguna diferencia.

Gracias por cualquier consejo, Giles

¿Fue útil?

Solución

El problema parece venir de pasar un tiempo de espera de argumento no válido.

Por lo menos en mi máquina, el primer fracaso no es ETIMEDOUT pero:

!!!!!! SEM2 sem_timedwait fallidos: argumento no válido, val ahora a 0

Ahora, si escribo:

  if (ts.tv_nsec >= 1000000)

(tenga en cuenta la adición de =) entonces funciona bien. Es otra pregunta de por qué el estado del semáforo se pone (presumiblemente) effed de modo que el tiempo de espera en los intentos posteriores o simplemente bloquea siempre en sem_wait recta. Se parece a un error en libc o el kernel.

Otros consejos

(Lo siento para dar una segunda respuesta, pero ésta sería demasiado complicado para limpiar simplemente con la edición)

La respuesta es, creo, ya en el post original de la pregunta.

Por lo tanto, mi pregunta es ¿por qué sem3 tiempo de espera, a pesar de que el semáforo tiene ha disparado y el valor es claramente 1? Nunca esperaría ver la línea 08 en la salida. Si el tiempo de espera (Debido, por ejemplo hilo 2 se ha estropeado o está tomando demasiado tiempo), el valor debe sea ??0. Y por qué funciona bien para 3 o 4 horas antes de entrar en este estado?

Así que el escenario es:

  1. hilo 2 tarda demasiado tiempo
  2. sem3 tiempos fuera en sem_timedwait
  3. hilo 3 se descheduled o lo que sea lo lleva a alcanzar el sem_getvalue
  4. hilo 2 se despierta y hace su sem_post en sem3
  5. hilo 3 temas de su sem_getvalue y ve un 1
  6. hilo 3 sucursales en el mal rama y no hace su sem_post en sem1

Esta condición de carrera es difícil de gatillo, básicamente, usted tiene que golpear la ventana de tiempo pequeña, donde un hilo ha tenido un problema en la espera del semáforo y luego lee el semáforo con la sem_getvalue. La ocurrencia de esa condición es mucho dependiente del medio ambiente (tipo de sistema, número de núcleos, la carga, las interrupciones IO) por lo que este explica por qué sólo se produce después de horas, si no en todos.

Tener el flujo de control depende de un sem_getvalue es generalmente una mala idea. La única atómica no bloquea el acceso a un sem_t es a través de sem_post y sem_trywait.

Así que este código de ejemplo de la cuestión tiene que condición de carrera. Esto no significa que el código del problema original que tenía gillez, tiene de hecho la misma condición de carrera. Quizás el ejemplo es demasiado simplista, y todavía muestra el mismo fenómeno para él.

Mi conjetura es, en su problema original había un sin protección sem_wait. Esa es una sem_wait que sólo se comprueba por su valor de retorno y no para errno en el caso de que no. EINTRs de hecho se producen en sem_wait como es natural, si el proceso tiene algunas IO. Usted acaba de hacer un do - while con cheque y restablecer de errno si se encuentra con un EINTR.

Esto es muy interesante. Aunque no he localizado el origen del error, (todavía mirando) He comprobado esto en Ubuntu 9.04 corriendo Linux 2.6.34.

No culpes a Ubuntu o cualquier otra distribución en él :-) Lo que es sin duda más importante aquí es la versión de gcc que está utilizando, 32 o 64 bits, etc, cuántos núcleos su sistema tiene. Así que por favor dar un poco más de información. Sin embargo, mirando a través de su código encontré varios lugares que sólo le podría traer un comportamiento inesperado:

  • comienza con el inicio, fundición int en la parte trasera void* adelante y hacia atrás, usted está buscando problemas. uso uintptr_t para que, si es necesario, pero aquí se no tienen excusa para pasar sólo reales punteros a los valores. &(int){ 1 } y algunos de fundición más cuerdo sería hacer el truco para C99.

  • ts.tv_nsec = (tv.tv_usec + 500000) es otro foco de problemas. El lado derecho podría ser de una anchura diferente, entonces el lado izquierdo. Hacer

    ts.tv_nsec = tv.tv_usec; ??

    ts.tv_nsec + = 500000;

  • La familia sem de funciones no están a salvo de interrupciones. Tales interrupciones pueden ser desencadenados por ejemplo por IO, ya que está haciendo printf etc. Comprobación del valor de retorno para -1 más o menos no es suficiente, pero en tal caso, usted debe comprobar errno y decidir si desea volver a intentarlo. Entonces tendría que hacer el nuevo cálculo del tiempo restante y cosas por el estilo si quieres ser precisos. Entonces página del manual de sem_timedwait tiene una lista de los diferentes códigos de error que pueden producirse y sus razones.

  • También la conclusión de que las cosas a partir de valores se obtiene a través de sem_getvalue. en un multi-threading / multi-proceso / multi-procesador el entorno de su hilo podría tener estado no programada entre la rentabilidad de sem_timedwait y sem_getvalue. Básicamente no se puede nada deducir de ello, la variable es sólo incidentalmente en el valor que observe. No extraer conclusiones de ello.

No tengo ni idea de lo que va mal y el código se ve muy bien también yo. Aquí hay algunas cosas que podría hacer de alguna manera no obtener más información.

  • utilizar un tiempo de espera diferente, tanto cortos como largos, y ver si se sigue produciendo el problema.
  • usar una versión no cronometrada, y ver si se bloquea el program.
  • tratar de modificar el comportamiento de su planificador del núcleo, por ejemplo, usando parámetros de línea de comandos del kernel, o el uso de procfs o sysfs.

Como se ha señalado por Jens, hay dos razas:

La primera es cuando evaluationg el valor del semáforo, después de la llamada a sem_timedwait. Esto no está cambiando el flujo de control de la pizca aspectos al semáforo. Ya sea la timedout hilo o no, todavía pasa por el "debería desencadenar el siguiente hilo" bloque.

El segundo está en el "¿Debo Reactivación el siguiente hilo" parte. Podríamos tener los siguientes eventos:

  1. Hilos n llamadas sem_getvalue(trigger) y obtiene un 1
  2. Thread n + 1 retornos de sem_timedwait y el semáforo va a 0
  3. Tema n no decide Post y el semáforo para estancias 0

Ahora, no puedo ver cómo esto podría desencadenar el comportamiento observado. Después de todo, ya Tema n + 1 se despertó de todos modos, que a su vez despertar hilo n + 2, que despertará hilo n etc ...

Si bien es posible obtener fallos, no puedo ver cómo esto podría conducir a la sistemática de tiempo de espera de un hilo.

Me dio el programa de un tiro en mi Ubuntu 10.04 x86_64 máquina Core i7.

Cuando se ejecuta con usleep (40000), la multa dirigió el programa durante media hora o algo aburrido.

Cuando se ejecuta con usleep (40), la multa dirigió el programa durante otra media hora, tal vez más, antes de que mi máquina se congeló. X murió. Control + alt + F1-7 murió. No podía ssh. (Lamentablemente, este teclado de Apple tonta no tiene una clave sysrq. Me gusta escribir sobre ella, pero estoy seguro que no es necesario F13, F14, F15 o. Lo haría cosas terribles para conseguir una clave sysrq adecuada.)

Y el más favorable:. NADA en mis registros me dice lo que sucedió

$ uname -a
Linux haig 2.6.32-22-generic #36-Ubuntu SMP Thu Jun 3 19:31:57 UTC 2010 x86_64 GNU/Linux

Al mismo tiempo, también estaba jugando un juego de Java en el navegador (Enviado por un usuario compañero de stackoverflow en busca de retroalimentación, diversión desvío :) - por lo que es posible que la JVM es responsable de las cosquillas algo para congelar mi máquina sólida .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top