stdout thread-safe in C su Linux?

Sta scrivendo su stdout usando printf thread-safe su Linux? Che dire dell’utilizzo del comando di write livello inferiore?

Non è specificato dallo standard C, dipende dall’implementazione della libreria standard C. In effetti, lo standard C non menziona nemmeno i thread, dal momento che alcuni sistemi (ad es. Sistemi embedded) non hanno il multithreading.

Nell’implementazione GNU ( glibc ), la maggior parte delle funzioni di livello superiore in stdio che gestiscono gli oggetti FILE* sono thread-safe. Quelli che di solito non sono unlocked nel loro nome (es. getc_unlocked(3) ). Tuttavia, la sicurezza del thread è a livello di chiamata per funzione: se si effettuano più chiamate a printf(3) , ad esempio, ciascuna di tali chiamate è garantita per l’output atomico, ma altri thread potrebbero stampare elementi tra le chiamate a printf() . Se si desidera garantire che una sequenza di chiamate I / O venga emessa atomicamente, è ansible circondarle con una coppia di flockfile(3)/funlockfile(3) per bloccare la maniglia FILE . Nota che queste funzioni sono rientranti, quindi puoi tranquillamente chiamare printf() tra loro, e questo non porterà a deadlock anche se pensate che printf() stesso fa una chiamata a flockfile() .

Le chiamate I / O di basso livello come write(2) dovrebbero essere thread-safe, ma non ne sono sicuro al 100%: write() effettua una chiamata di sistema nel kernel per eseguire I / O. Come esattamente ciò accade dipende dal kernel che stai usando. Potrebbe essere l’istruzione sysenter o l’istruzione int (interrupt) su sistemi precedenti. Una volta all’interno del kernel, è compito del kernel assicurarsi che l’I / O sia sicuro per i thread. In un test che ho appena fatto con il Darwin Kernel versione 8.11.1, write(2) sembra essere thread-safe.

Se lo chiami “thread-safe” dipende dalla tua definizione di thread-safe. POSIX richiede che le funzioni stdio utilizzino il blocco, quindi il tuo programma non si bloccherà, corromperà gli stati dell’object FILE , ecc. Se usi printf simultaneamente da più thread. Tuttavia, tutte le operazioni stdio sono formalmente specificate in termini di chiamate ripetute a fgetc e fputc , quindi non è garantita l’atomicità su più larga scala. Vale a dire, se i thread 1 e 2 provano a stampare "Hello\n" e "Goodbye\n" allo stesso tempo, non c’è alcuna garanzia che l’output sia "Hello\nGoodbye\n" o "Goodbye\nHello\n" . Potrebbe anche essere "HGelolodboy\ne\n" . In pratica, la maggior parte delle implementazioni acquisirà un singolo blocco per l’intera chiamata di scrittura di livello superiore semplicemente perché è più efficiente, ma il tuo programma non dovrebbe assumerlo. Ci possono essere casi d’angolo in cui ciò non viene fatto; ad esempio un’implementazione potrebbe probabilmente omettere il blocco su stream non bufferizzati.

Modifica: il testo precedente sull’atomicità non è corretto. POSIX garantisce che tutte le operazioni stdio siano atomiche, ma la garanzia è nascosta nella documentazione di flockfile : http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html

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

È ansible utilizzare le flockfile , ftrylockfile e funlockfile per ottenere le scritture atomiche di chiamata più grande della singola funzione.

Sono entrambi thread-safe al punto che l’applicazione non si bloccherà se più thread li chiamano sullo stesso descrittore di file. Tuttavia, senza alcun blocco a livello di applicazione, tutto ciò che è scritto potrebbe essere interfogliato.

È thread-safe; printf dovrebbe essere rientranti e non causerai alcuna stranezza o corruzione nel tuo programma.

Non è ansible garantire che l’output di una discussione non inizi a metà dell’output da un altro thread. Se ti interessa, devi sviluppare il tuo codice di uscita bloccato per impedire l’accesso multiplo.

C ha ottenuto un nuovo standard da quando è stata posta questa domanda (e ultima risposta).

C11 ora viene fornito con supporto multithreading e indirizza il comportamento multithread dei flussi:

§ 7.2.2.2 Flussi

¶7 Ogni stream ha un blocco associato che viene utilizzato per impedire la corsa dei dati quando più thread di esecuzione accedono a un stream e per limitare l’interleaving delle operazioni di streaming eseguite da più thread. Solo una discussione può contenere questo blocco alla volta. Il blocco è rientrante: un singolo thread può trattenere il blocco più volte in un dato momento.

¶8 Tutte le funzioni che leggono, scrivono, posizionano o interrogano la posizione di un stream bloccano il stream prima di accedervi. Rilasciano il blocco associato allo stream quando l’accesso è completo.

Quindi, un’implementazione con thread C11 deve garantire che l’uso di printf sia thread-safe.

Se l’atomicità (come in nessun interleaving 1 ) è garantita, non è stato così chiaro a prima vista, perché lo standard parlava di limitare l’ interlacciamento, al contrario della prevenzione , che imponeva per le corse di dati.

Mi chino verso ciò che è garantito. Lo standard parla di limitare l’ interleaving, poiché è ancora permesso che si verifichi un interleaving che non modifichi l’esito; ad esempio , per fseek alcuni byte, fseek indietro di più e fwrite fino allo scostamento originale, in modo che entrambi i fwrite siano back-to-back. L’implementazione è gratuita per riordinare questi 2 fwrite e unirli in un’unica scrittura.


1 : Vedi il testo barrato nella risposta di R. per un esempio.