2012-11-02 15 views
5

Scrivo un programma utilizzando pthread.output strano quando uso pthread e printf

Ambiente: Windows 7, CYGWIN_NT-i686 6.1 Cygwin, gcc (GCC) 4.5.3

Il codice sorgente

#include<stdio.h> 
#include<pthread.h> 

void *th_func(void *p) 
{ 
    int iLoop = 0; 

    for(iLoop = 0;iLoop<100;iLoop++) 
    { 
     printf("Thread Thread Thread Thread\n"); 
    } 

    return; 
} 

int main() 
{ 
    int iLoop = 0; 
    pthread_t QueThread; 

    printf("Main : Start Main\n"); 

    printf("Main : Start Create Thread\n"); 
    pthread_create(&QueThread,NULL,th_func,NULL); 
    printf("Main : End Create Thread\n"); 

    for(iLoop = 0;iLoop<100;iLoop++) 
    { 
     printf("Main Main Main Main\n"); 
    } 

    pthread_join(QueThread,NULL); 

    printf("Main : End Main\n"); 

    printf("---------------\n"); 

    return 0; 
} 

Quando compilo il codice sorgente, non ci sono avvisi o errori, ma è l'output è strano.

Una parte di esso è uscita

Main : Start Main 
Main : Start Create Thread 
Thread Thread Thread ThreThread Thread Thread Thread 
Main Main Main Main 
Thread Thread Thread Thread 
Main Main Main Main 

Voglio sapere la causa di tale fenomeno.

In questa uscita, Main : End Create Thread non viene stampato completamente. E alla riga 3 sparisce una nuova riga \n alla fine di "Thread Thread Thread Thread\n".

L'output di tutti è simile a questo? Non si verifica ogni volta, ma si verifica a volte.

Se utilizzo il mutex per chiamare printf in modo sicuro, l'output strano sembra essere arrestato.

POSIX dice che printf è thread-safe, e secondo Cygwin.com, cygwin fornisce API in stile posix. Tuttavia, c'è l'uscita inaspettata.

È printf veramente thread-safe?

Ho eseguito lo stesso programma 100 volte in Linux (Ubuntu) e questo output non si è verificato.

Inoltre, non ho capito il motivo per cui alcune parole sull'output sono scomparse.

+2

Si vedono solo due thread in uscita intercalati. – CCoder

+2

Thread-safe non significa che sia atomico. – Barmar

+0

Questa domanda non ha nulla a che fare con il duplicato identificato. Questa ricerca riguarda specificatamente se POSIX specifica che 'printf()' dovrebbe essere atomico tra i thread in un processo. Il duplicato identificato riguarda la pianificazione generale dei thread e le condizioni di gara. –

risposta

3

Sembra che potrebbe essere un bug in Cygwin, o forse qualcosa non è configurato correttamente.Diverse risposte indicano che "thread safe" promette solo che la funzione non causerà danni al programma e che la sicurezza del thread non significa necessariamente che una funzione è "atomica". Ma, per quanto ne so, POSIX non definisce formalmente "thread safe" (se qualcuno ha un puntatore a tale definizione, per favore pubblicalo in un commento).

Tuttavia, non solo POSIX specifica che printf() è sicuro filo, POSIX also specifies che:

Tutte le funzioni che fanno riferimento (FILE *) oggetti devono comportarsi come se utilizzano flockfile() e funlockfile() internamente ottenere la proprietà di questi oggetti (FILE *).

Da printf() fa riferimento implicitamente l'oggetto stdoutFILE*, tutte printf() chiamate dovrebbero essere atomico rispetto all'altro (e qualsiasi altra funzione che utilizza stdout).

Si noti che questo potrebbe non essere vero su altri sistemi, ma nella mia esperienza è vero per molti sistemi multi-thread.

+0

La definizione di "thread-safe" si trova in [sezione 3.399] (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_399). Per quanto riguarda il blocco dei file, dal momento che 'printf()' è una sequenza di chiamate 'putc()', solo quest'ultima in effetti fa riferimento a 'FILE *' e ha bisogno di ottenere la proprietà. – Barmar

+0

Grazie per il puntatore alla definizione 'thread-safe' (ma devo dire che penso che lasci molto a desiderare). Inoltre, non penso che sia la corretta interpretazione del riferimento a un oggetto '(FILE *)'. So che è solo un appello all'autorità, ma ecco un post di David Butenhof (autore di "Programming with POSIX Threads") in cui afferma che 'printf()' deve essere atomico: http://newsgroups.derkeiler.com/Archive /Comp/comp.programming.threads/2009-06/msg00058.html –

+0

Ho iniziato a pensare che sia un bug in Cygwin. Quando ho eseguito il programma in Linux, l'output strano non si è verificato. –

0

Solo perché una funzione è thread-safe, non significa che sia atomica.

Nel tuo caso, se vuoi assicurarti che il tuo output non diventi interlacciato, devi usare un mutex per assicurarti che un solo thread chiami printf alla volta.

+0

Anno, un mutex impedisce che l'output diventi interlacciato. Quando compilo ed eseguo il programma in Linux, l'output non si verifica .. –

0

I thread si comportano in questo modo per un motivo. Se i thread venivano eseguiti uno dopo l'altro e non "allo stesso tempo" (in modo intercalato), non ci sarebbe alcun punto in questo tipo di "concorrenza". Quando usi mutex, i thread verranno bloccati in base alle tue intenzioni e genereranno l'output previsto.

Inoltre, si scrive return; in una funzione che restituisce void * e che è un comportamento non definito, quindi tutto può succedere quando si esegue il programma.

+0

Non ho saputo che il ritorno non dovrebbe essere scritto nella funzione void! Grazie! Cancellare il reso; , ma si verifica lo stesso fenomeno.La tua prima frase significa che non è la stessa cosa tra l'interleaving e la concorrenza? –

0

Metto questo in un modo semplice con due thread che tentano di accedere a una risorsa. E inoltre non ci sono controlli di priorità tipo o qualcosa come un mutex. In teoria, i thread senza mutex o priorità vengono assegnati con risorse in modo casuale. prova a creare due thread con una stampa thread sì e l'altra no. troverai questo comportamento insolito. Ricorda anche che il tempo di esecuzione è diverso per i diversi thread in questo caso. Se provi le stesse cose con un thread scrivendo le informazioni su un file e un altro ragazzo scrivendo su console. Non incontrerai questo problema. Spero che questo aiuti ....

+0

Quando eseguo il programma Sì/No, Sì o No vengono visualizzati a caso! –

4

serie La POSIX ha funzioni come putc_unlocked() in cui il commento dice:

versioni delle funzioni getc(), getchar(), putc() e putchar(), rispettivamente chiamati getc_unlocked(), getchar_unlocked(), putc_unlocked(), e deve essere fornita putchar_unlocked() che sono funzionalmente equivalente alle versioni originali, con l'eccezione che non è necessario che siano implementati in modo thread-safe. Possono essere utilizzati solo in un ambito protetto da flockfile() (o ftrylockfile()) e funlockfile(). Queste funzioni possono essere tranquillamente utilizzate in un programma multi-thread se e solo se vengono chiamate mentre il thread chiamante possiede l'oggetto (FILE *), come nel caso dopo una chiamata riuscita alle funzioni flockfile() o ftrylockfile().

Ciò indica chiaramente che le funzioni di basso livello per I/O a carattere singolo sono normalmente thread-safe. Tuttavia, indica anche che il livello di granularità è un'operazione di output a carattere singolo. La specifica per printf() dice:

caratteri generati da fprintf() e printf() vengono stampate come se fputc() era stato chiamato.

E per putc(), che dice:

La funzione putc() deve essere equivalente a fputc(), tranne che se si è implementato come una macro può valutare il flusso di più di una volta, così l'argomento non deve mai essere un'espressione con effetti collaterali.

La pagina per fputc() non dice nulla su thread-safety, quindi è necessario cercare altrove tali informazioni.

Un'altra section descrive fili e dice:

Tutte le funzioni definite da questo volume di POSIX.1-2008 sono thread-safe, tranne che le seguenti funzioni non devono essere thread-safe.

E l'elenco seguente include le funzioni *_unlocked().

Quindi, printf() e fputc() devono essere thread-safe, ma la scrittura da printf() è fatto 'come se' da fputc(), così l'interleaving di uscita tra fili possono essere a livello di carattere, che è più o meno coerente con quello che vedi. Se si desidera effettuare chiamate a printf() non interlacciati, sarà necessario utilizzare le chiamate flockfile() e funlockfile() per fornire la proprietà del thread di stdout mentre viene eseguito il numero printf(). Allo stesso modo per fprintf(). Si potrebbe scrivere una funzione fprintf_locked() abbastanza facilmente per ottenere questo risultato:

int fprintf_locked(FILE *fp, const char *format, ...) 
{ 
    flockfile(fp); 
    va_list args; 
    va_start(args, format); 
    int rc = vfprintf(fp, format, args); 
    va_end(args); 
    funlockfile(fp); 
    return rc; 
} 

è possibile inserire un fflush(fp) in là se si voleva. Potresti anche avere un vfprintf_locked() e avere la funzione sopra chiamata per eseguire le operazioni di blocco, formattazione, (flush) e sblocco. Probabilmente è come lo codificherei, confidando che il compilatore inline il codice se fosse appropriato e fattibile. Anche il supporto delle versioni che usano stdout è abbastanza semplice.

nota il frammento di specifiche POSIX per flockfile() citato da Michael Burr nei suoi answer:

Tutte le funzioni che fanno riferimento (FILE *) gli oggetti, ad eccezione di quelli con i nomi che terminano in _unlocked, deve comportarsi come se usano flockfile() e funlockfile() internamente per ottenere la proprietà di questi oggetti (FILE *).

A parte le parentesi dispari intorno alla FILE *, queste righe impatto tutti gli altri standard di I funzioni I/O, ma devi sapere che queste righe esistono in una delle pagine meno utilizzati di frequente. Pertanto, la mia funzione non dovrebbe essere necessaria. Se trovi un'implementazione aberrante di fprintf() che non blocca il file, allora potrebbe essere utilizzata la funzione fprintf_locked(), ma dovrebbe essere eseguita solo in segno di protesta: la libreria dovrebbe farlo comunque per te.

+0

Non ho ancora capito la tua risposta, ma ho afferrato quello che mi hai detto. In breve, dici che la stringa stampata da più thread usando printf() può essere mescolata a livello di personaggio, vero? Grazie per il tempo dedicato alla ricerca di molte informazioni per rispondere alla mia domanda! –

+0

Sì, sto dicendo che le stringhe stampate da 'printf()' potrebbero essere intercalate da thread diversi a livello di carattere. È stato interessante provare a mettere insieme tutti i pezzi; Non sono sicuro che sia abbastanza solido come vorrei, ma penso che il percorso sia abbastanza buono - a meno che qualcuno non abbia una derivazione alternativa e una conclusione diversa. –