50 Stimmen

Ist stdout thread-safe in C auf Linux?

Ist das Schreiben auf stdout mit printf auf Linux threadsicher? Wie sieht es mit dem Verwenden des niederstufigen write Befehls aus?

64voto

Adam Rosenfield Punkte 373807

Es wird nicht durch den C-Standard definiert -- es hängt von Ihrer Implementierung der C-Standardbibliothek ab. Tatsächlich erwähnt der C-Standard nicht einmal Threads, da bestimmte Systeme (z.B. eingebettete Systeme) kein Multithreading haben.

In der GNU-Implementierung (glibc) sind die meisten der höheren Funktionen in stdio, die mit FILE*-Objekten umgehen, threadsicher. Diejenigen, die es nicht sind, haben normalerweise unlocked in ihrem Namen (z.B. getc_unlocked(3)). Die Thread-Sicherheit erfolgt jedoch auf Funktionsebene: Wenn Sie mehrere Aufrufe von printf(3) machen, wird garantiert, dass jeder dieser Aufrufe atomar ausgegeben wird, aber andere Threads könnten Dinge zwischen Ihren Aufrufen von printf() ausgeben. Wenn Sie sicherstellen möchten, dass eine Sequenz von Ein-/Ausgabefunktionen atomar ausgegeben wird, können Sie sie mit einem Paar von flockfile(3)/funlockfile(3)-Aufrufen umgeben, um den FILE-Handle zu sperren. Beachten Sie, dass diese Funktionen reentrant sind, sodass Sie sicher printf() zwischen ihnen aufrufen können, und dies nicht zu einem Deadlock führt, obwohl printf() selbst einen Aufruf von flockfile() macht.

Die Low-Level-E/A-Aufrufe wie write(2) sollten threadsicher sein, aber ich bin mir nicht zu 100% sicher - write() führt ein Systemaufruf in den Kernel aus, um die E/A durchzuführen. Wie genau dies geschieht, hängt davon ab, welchen Kernel Sie verwenden. Es könnte die sysenter-Anweisung sein oder die int (Interrupt)-Anweisung auf älteren Systemen. Sobald im Kernel, liegt es am Kernel, sicherzustellen, dass die E/A threadsicher ist. In einem Test, den ich gerade mit der Darwin-Kernelversion 8.11.1 gemacht habe, scheint write(2) threadsicher zu sein.

27 Stimmen

Diese Antwort ignoriert, dass die Frage mit unix / linux gekennzeichnet war. POSIX erfordert, dass stdio thread-sicher ist, was ziemlich unglücklich ist, da dies die Leistung beeinträchtigt und es keine praktikable Möglichkeit gibt, auf dasselbe FILE aus mehreren Threads heraus zu operieren (Daten werden hoffnungslos durcheinander kommen; die Atomarität liegt nur auf Zeichenebene).

1 Stimmen

Manchmal ist es völlig in Ordnung, wenn die Ausgabe zwischengeschaltet ist, z.B. während des Loggens über printf aus mehreren Threads.

1 Stimmen

@couling Ich denke, er meint, dass die Thread-Sicherheit nutzlos ist, weil sowieso alles durcheinander kommt - es sei denn, Sie verwenden trotzdem eine explizite f[un]lockfile.

26voto

Ob Sie es "thread-sicher" nennen würden, hängt von Ihrer Definition von thread-sicher ab. POSIX erfordert, dass stdio-Funktionen Locking verwenden, sodass Ihr Programm nicht abstürzt, den FILE-Objektzustand beschädigt usw., wenn Sie printf gleichzeitig aus mehreren Threads verwenden. Allerdings werden alle stdio-Operationen formell in Bezug auf wiederholte Aufrufe von fgetc und fputc ``spezifiziert, sodass keine größere atomare Garantie besteht. Das heißt, wenn die Threads 1 und 2 versuchen, `"Hello\n"` und `"Goodbye\n"` gleichzeitig zu drucken, ist nicht garantiert, dass die Ausgabe entweder `"Hello\nGoodbye\n"` oder `"Goodbye\nHello\n"` sein wird. Es könnte genauso gut `"HGelolodboy\ne\n"` sein. In der Praxis werden die meisten Implementierungen wahrscheinlich einfach deshalb ein einzelnes Lock für den gesamten höherstufigen Schreibaufruf erwerben, weil es effizienter ist, aber Ihr Programm sollte das nicht voraussetzen. Es können Randfälle geben, in denen dies nicht geschieht; beispielsweise könnte eine Implementierung das Locking bei nicht gepufferten Streams vollständig auslassen.``

`

Bearbeitung: Der obige Text über Atomarität ist inkorrekt. POSIX garantiert, dass alle stdio-Operationen atomar sind, aber die Garantie ist in der Dokumentation für flockfile versteckt: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html

Alle Funktionen, die (FILE)-Objekte referenzieren, sollen sich so verhalten, als würden sie intern flockfile() und funlockfile() verwenden, um den Besitz dieser (FILE)-Objekte zu erlangen.

Sie können die Funktionen flockfile, ftrylockfile und funlockfile selbst verwenden, um größere als einzelne Funktionsaufrufe atomare Schreibvorgänge zu erreichen.

`` ```

17voto

Dave Ray Punkte 38948

Sie sind beide thread-sicher bis zu dem Punkt, an dem Ihre Anwendung nicht abstürzt, wenn mehrere Threads sie auf demselben Dateideskriptor aufrufen. Ohne eine Anwendungsebene-Sperrung könnten jedoch die geschriebenen Inhalte durcheinander geraten.

10voto

a3f Punkte 8207

C hat einen neuen Standard erhalten, seit diese Frage gestellt wurde (und zuletzt beantwortet).

C11 kommt jetzt mit Multithreading-Unterstützung und behandelt das Multithreading-Verhalten von Streams:

§7.21.2 Streams

¶7 Jeder Stream hat ein zugehöriges Schloss, das verwendet wird, um Datenkonflikte zu verhindern, wenn mehrere Ausführungsstränge auf einen Stream zugreifen, und um die Verzweigung von Streamoperationen, die von mehreren Threads ausgeführt werden, zu beschränken. Nur ein Thread kann dieses Schloss gleichzeitig halten. Das Schloss ist reentrant: Ein einzelner Thread kann das Schloss mehrmals zu einem bestimmten Zeitpunkt halten.

¶8 Alle Funktionen, die einen Stream lesen, schreiben, positionieren oder die Position eines Streams abfragen, sperren den Stream, bevor sie darauf zugreifen. Sie geben das mit dem Stream verknüpfte Schloss frei, wenn der Zugriff abgeschlossen ist.

Also muss eine Implementierung mit C11-Threads garantieren, dass die Verwendung von printf threadsicher ist.

Ob die Atomarität (wie bei keinem Verzweigen) garantiert ist, war mir auf den ersten Blick nicht klar, weil der Standard von der Beschränkung des Verzweigens sprach, im Gegensatz zur Verhinderung, was er für Datenkonflikte vorschrieb.

Ich tendiere dazu zu glauben, dass es garantiert ist. Der Standard spricht von der Beschränkung des Verzweigens, da bestimmte Verzweigungen, die das Ergebnis nicht ändern, immer noch erlaubt sind; z.B. fwrite einige Bytes, fseek zurück und fwrite bis zur ursprünglichen Position, sodass beide fwrites hintereinander liegen. Die Implementierung kann diese 2 fwrites neu anordnen und sie zu einem einzelnen Schreibvorgang zusammenführen.


1: Siehe den durchgestrichenen Text in der Antwort von R.. für ein Beispiel.

6voto

Adam Hawes Punkte 5403

Es ist threadsicher; printf sollte reentrant sein, und Sie werden keine Seltsamkeiten oder Korruption in Ihrem Programm verursachen.

Sie können nicht garantieren, dass Ihre Ausgabe von einem Thread nicht mitten durch die Ausgabe eines anderen Threads beginnt. Wenn Ihnen das wichtig ist, müssen Sie Ihren eigenen gesperrten Ausgabe-Code entwickeln, um mehrfachen Zugriff zu verhindern.

0 Stimmen

Alle Aufrufe von printf verwenden wahrscheinlich denselben Puffer zum Erstellen des Strings. Viele Implementierungen teilen auch einen Puffer zwischen scanf und printf, was zu einigen seltsamen, von Debugging abhängigen Fehlern führen kann.

3 Stimmen

Ich weiß nicht, was andere tun, aber die GNU C-Bibliothek ist standardmäßig threadsicher, daher wird sie nicht denselben Puffer verwenden.

2 Stimmen

Ich denke nicht, dass printf reentrant ist, siehe [stackoverflow.com/questions/3941271/…](http://stackoverflow.com/questions/3941271/why-are-malloc-and-printf-said-as-non reentrant "Warum sind malloc und printf als nicht-reentrant bezeichnet")

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X