2011-08-19 6 views
15

Si consideri il seguente codice sorgente, che è completamente compatibile con POSIX:Come rendere pthread_cond_timedwait() affidabile rispetto alle manipolazioni dell'orologio di 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; 
} 

stampa la data corrente del sistema ogni 5 secondi, invece, si fa un sonno di 3 secondi tra ottenere l'ora di sistema corrente (gettimeofday) e la condizione attesa (pthread_cond_timedwait).

Subito dopo aver stampato "sleep (...)", prova a impostare l'orologio di sistema due giorni fa. Che succede? Bene, invece di aspettare altri 2 secondi come al solito, pthread_cond_timedwait attende due giorni e 2 secondi.

Come posso risolvere il problema?
Come posso scrivere codice conforme POSIX, che non si interrompe quando l'utente manipola l'orologio di sistema?

Si prega di tenere presente che l'orologio di sistema potrebbe cambiare anche senza l'interazione dell'utente (ad esempio un client NTP potrebbe aggiornare l'orologio automaticamente una volta al giorno). L'impostazione dell'orologio nel futuro non è un problema, causerà solo il risveglio del sonno precoce, che di solito non è un problema e che puoi facilmente "rilevare" e gestire di conseguenza, ma impostare l'orologio nel passato (ad esempio perché era l'esecuzione in futuro, NTP rilevato e risolto) può causare un grosso problema.

PS:
pthread_condattr_setclock()CLOCK_MONOTONIC esiste sul mio sistema. Queste sono obbligatorie per le specifiche POSIX 2008 (parte di "Base") ma la maggior parte dei sistemi segue ancora le specifiche POSIX 2004 fino ad oggi e nelle specifiche POSIX 2004 queste due erano facoltative (Advanced Realtime Extension).

+2

Sono così infastidito che questa funzione abbia una struttura 'timespec' e usi il tempo assoluto. Penso che userò un approccio 'pipe()'/'select()' per aggirarlo. – mpontillo

risposta

4

Interessante, io non ho incontrato che il comportamento prima, ma, poi di nuovo, io non sono l'abitudine di pasticciare in giro con il mio orario di sistema che molto :-)

Supponendo che si sta facendo che per un motivo valido, una soluzione possibile (sebbene kludgy) è quella di avere un altro thread il cui unico scopo è di calciare periodicamente la variabile condition per risvegliare eventuali thread così interessati.

In altre parole, qualcosa di simile a:

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

Il codice che è attesa per la variabile di condizione di essere preso a calci dovrebbe essere controllando suo predicato in ogni caso (a prendersi cura di wakeups spuri) quindi questo non dovrebbe avere alcun reale effetto negativo sulla funzionalità.

È un leggero calo di prestazioni, ma una volta ogni dieci secondi non dovrebbe essere un problema. In realtà è pensato solo per occuparsi delle situazioni in cui (per qualsiasi motivo) l'attesa programmata attenderà a lungo.


Un'altra possibilità è di riprogettare l'applicazione in modo da non aver bisogno di attese temporizzate.

In situazioni in cui i fili devono essere risvegliati per qualche motivo, è invariabilmente da un altro thread che è perfettamente in grado di calciare una variabile di condizione per svegliarne uno (o trasmettere per svegliarne molti).

Questo è molto simile al thread di kicking che ho citato sopra ma più come parte integrante della tua architettura di un bolt-on.

3

Puoi difendere il tuo codice contro questo problema. Un modo semplice è quello di avere una discussione il cui unico scopo è quello di osservare l'orologio di sistema. Si mantiene un elenco globale collegato di variabili di condizione e se il thread di watch watch vede saltare un clock di sistema, trasmette tutte le variabili di condizione sull'elenco. Quindi, è sufficiente avvolgere pthread_cond_init e pthread_cond_destroy con il codice che aggiunge/rimuove la variabile di condizione da/alla lista globale collegata. Proteggi l'elenco collegato con un mutex.