2011-08-19 4 views
15

Consideremos el siguiente código, que es totalmente compatible con POSIX:¿Cómo hacer pthread_cond_timedwait() robusto contra las manipulaciones del reloj del sistema?

#include <stdio.h> 
#include <limits.h> 
#include <stdint.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <sys/time.h> 

int main (int argc, char ** argv) { 
    pthread_cond_t c; 
    pthread_mutex_t m; 
    char printTime[UCHAR_MAX]; 

    pthread_mutex_init(&m, NULL); 
    pthread_cond_init(&c, NULL); 

    for (;;) { 
     struct tm * tm; 
     struct timeval tv; 
     struct timespec ts; 

     gettimeofday(&tv, NULL); 

     printf("sleep (%ld)\n", (long)tv.tv_sec); 
     sleep(3); 

     tm = gmtime(&tv.tv_sec); 
     strftime(printTime, UCHAR_MAX, "%Y-%m-%d %H:%M:%S", tm); 
     printf("%s (%ld)\n", printTime, (long)tv.tv_sec); 

     ts.tv_sec = tv.tv_sec + 5; 
     ts.tv_nsec = tv.tv_usec * 1000; 

     pthread_mutex_lock(&m); 
     pthread_cond_timedwait(&c, &m, &ts); 
     pthread_mutex_unlock(&m); 
    } 
    return 0; 
} 

imprime la fecha actual del sistema cada 5 segundos, sin embargo, hace un sueño de 3 segundos entre obtener la hora actual del sistema (gettimeofday) y la condición espera (pthread_cond_timedwait).

Justo después de imprimir "dormir (...)", intente configurar el reloj del sistema dos días pasados. ¿Lo que pasa? Bueno, en lugar de esperar 2 segundos más en la condición que normalmente lo hace, pthread_cond_timedwait ahora espera dos días y 2 segundos.

¿Cómo puedo solucionar eso?
¿Cómo puedo escribir el código que cumple con POSIX, que no se rompe cuando el usuario manipula el reloj del sistema?

Tenga en cuenta que el reloj del sistema podría cambiar incluso sin interacción del usuario (por ejemplo, un cliente NTP podría actualizar el reloj automáticamente una vez al día). Establecer el reloj en el futuro no es un problema, solo hará que el sueño se despierte temprano, lo que generalmente no es un problema y que puede "detectar" y manejar fácilmente en consecuencia, pero dejando el reloj en el pasado (por ejemplo, porque era funcionando en el futuro, NTP detectó eso y lo solucionó) puede causar un gran problema.

PS:
Ni pthread_condattr_setclock() ni CLOCK_MONOTONIC existe en mi sistema. Esos son obligatorios para la especificación POSIX 2008 (parte de "Base"), pero la mayoría de los sistemas todavía siguen la especificación POSIX 2004 a día de hoy y en la especificación POSIX 2004 estos dos eran opcionales (Extensión en tiempo real avanzada).

+2

Estoy tan enojado que esta función toma una estructura 'timespec' y usa tiempo absoluto. Creo que voy a utilizar un enfoque 'pipe()'/'select()' para evitarlo. – mpontillo

Respuesta

4

interesante, no he encontrado que el comportamiento antes, pero, de nuevo, no estoy en el hábito de curioseaba con mi tiempo del sistema que mucho :-)

Suponiendo que está haciendo que para una razón válida, una solución posible (aunque kludgy) es tener otro hilo cuyo único propósito es patear periódicamente la variable de condición para reactivar los hilos afectados.

En otras palabras, algo así como:

while (1) { 
    sleep (10); 
    pthread_cond_signal (&condVar); 
} 

Su código que se espera de la variable condición de ser expulsado debe comprobar su predicado de todos modos (para cuidar de activaciones falsas) por lo que no debe tener ningún verdadero efecto perjudicial sobre la funcionalidad.

Es un golpe leve rendimiento, pero una vez cada diez segundos no debería ser un gran problema. En realidad, solo pretende ocuparse de las situaciones en las que (por alguna razón) su espera temporizada estará esperando mucho tiempo.


Otra posibilidad es volver a diseñar su aplicación para que no necesite esperas temporizadas en absoluto.

En situaciones donde los hilos necesitan ser despertados por alguna razón, es invariablemente por otro hilo que es perfectamente capaz de patear una variable de condición para activar uno (o la radiodifusión para reactivarlos).

Esto es muy similar al hilo de patada que mencioné anteriormente, pero más como una parte integral de su arquitectura que un perno.

3

Puede defender su código contra este problema. Una manera fácil es tener un hilo cuyo único propósito es mirar el reloj del sistema. Mantiene una lista vinculada global de variables de condición, y si el hilo del observador de reloj ve saltar un reloj del sistema, difunde cada variable de condición en la lista. Luego, simplemente envuelve pthread_cond_init y pthread_cond_destroy con un código que agrega/elimina la variable de condición a/desde la lista global enlazada. Proteja la lista vinculada con un mutex.

Cuestiones relacionadas