Ist cout synchronisiert / threadsicher?

112

Im Allgemeinen gehe ich davon aus, dass Streams nicht synchronisiert sind. Es ist Sache des Benutzers, eine entsprechende Sperrung vorzunehmen. Erhalten Dinge wie couteine Sonderbehandlung in der Standardbibliothek?

Das heißt, wenn mehrere Threads schreiben, coutkönnen sie das coutObjekt beschädigen? Ich verstehe, dass selbst wenn synchronisiert, Sie immer noch zufällig verschachtelte Ausgabe erhalten würden, aber ist diese Verschachtelung garantiert. Ist es sicher, coutvon mehreren Threads aus zu verwenden?

Ist dieser Anbieter abhängig? Was macht gcc?


Wichtig : Bitte geben Sie eine Referenz für Ihre Antwort an, wenn Sie "Ja" sagen, da ich einen Beweis dafür benötige.

Mein Anliegen ist auch nicht die zugrunde liegenden Systemaufrufe, diese sind in Ordnung, aber die Streams fügen eine Pufferschicht hinzu.

edA-qa mort-ora-y
quelle
2
Dies ist herstellerabhängig. C ++ (vor C ++ 0x) kennt keine mehreren Threads.
Sven
2
Was ist mit c ++ 0x? Es definiert ein Speichermodell und was ein Thread ist. Vielleicht sind diese Dinge in der Ausgabe durchgesickert?
Rubenvb
2
Gibt es Anbieter, die es threadsicher machen?
edA-qa mort-ora-y
Hat jemand einen Link zum neuesten vorgeschlagenen C ++ 2011-Standard?
edA-qa mort-ora-y
4
In gewissem Sinne printfleuchtet hier die gesamte Ausgabe auf stdouteinmal. bei Verwendung würde std::coutjedes Glied der Ausdruckskette separat ausgegeben werden an stdout; dazwischen kann es einen anderen Thread geben, auf den geschrieben wird stdout, wodurch die Reihenfolge der endgültigen Ausgabe durcheinander gebracht wird.
Legends2k

Antworten:

106

Der C ++ 03-Standard sagt nichts darüber aus. Wenn Sie keine Garantie für die Thread-Sicherheit von etwas haben, sollten Sie es als nicht thread-sicher behandeln.

Von besonderem Interesse ist hier die Tatsache, dass coutgepuffert ist. Selbst wenn sich die Aufrufe von write(oder was auch immer diesen Effekt in dieser bestimmten Implementierung bewirkt) gegenseitig ausschließen, kann der Puffer von den verschiedenen Threads gemeinsam genutzt werden. Dies führt schnell zu einer Beschädigung des internen Status des Streams.

Und selbst wenn der Zugriff auf den Puffer garantiert threadsicher ist, was wird Ihrer Meinung nach in diesem Code passieren?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

Sie möchten wahrscheinlich, dass jede Zeile hier in gegenseitigem Ausschluss handelt. Aber wie kann eine Implementierung das garantieren?

In C ++ 11 haben wir einige Garantien. Das FDIS sagt in §27.4.1 [iostream.objects.overview] Folgendes:

Der gleichzeitige Zugriff auf die formatierten und unformatierten Eingabe- (§27.7.2.1) und Ausgabefunktionen (§27.7.3.1) eines synchronisierten (§27.5.3.4) Standard-Iostream-Objekts oder auf einen Standard-C-Stream durch mehrere Threads darf nicht zu einem Datenrennen führen (§ 1.10). [Hinweis: Benutzer müssen weiterhin die gleichzeitige Verwendung dieser Objekte und Streams durch mehrere Threads synchronisieren, wenn sie verschachtelte Zeichen vermeiden möchten. - Endnote]

Sie erhalten also keine beschädigten Streams, müssen diese jedoch manuell synchronisieren, wenn die Ausgabe kein Müll sein soll.

R. Martinho Fernandes
quelle
2
Technisch wahr für C ++ 98 / C ++ 03, aber ich denke, das weiß jeder. Dies beantwortet jedoch nicht die beiden interessanten Fragen: Was ist mit C ++ 0x? Was typische Implementierungen tatsächlich tun ?
Nemo
1
@ edA-qa mort-ora-y: Nein, das hast du falsch gemacht. C ++ 11 definiert klar , dass die Standard - Stromobjekte können synchronisiert werden und behalten gut definierte Verhalten, nicht , dass sie standardmäßig sind.
ildjarn
12
@ildjarn - Nein, @ edA-qa mort-ora-y ist korrekt. Solange dies cout.sync_with_stdio()zutrifft, ist die Verwendung coutder Ausgabe von Zeichen aus mehreren Threads ohne zusätzliche Synchronisierung genau definiert, jedoch nur auf der Ebene einzelner Bytes. So cout << "ab";und cout << "cd"in verschiedenen Threads ausgeführt, kann beispielsweise ausgegeben werden acdb, kann aber kein undefiniertes Verhalten verursachen.
JohannesD
4
@JohannesD: Da sind wir uns einig - es ist mit der zugrunde liegenden C-API synchronisiert. Mein Punkt ist, dass es nicht auf nützliche Weise "synchronisiert" wird, dh man braucht immer noch eine manuelle Synchronisation, wenn sie keine Mülldaten wollen.
ildjarn
2
@ildjarn, mir geht es gut mit den Mülldaten, das bisschen verstehe ich. Ich interessiere mich nur für den Zustand des Datenrennens, der jetzt klar zu sein scheint.
edA-qa mort-ora-y
16

Das ist eine gute Frage.

Erstens hat C ++ 98 / C ++ 03 kein Konzept von "Thread". In dieser Welt ist die Frage also bedeutungslos.

Was ist mit C ++ 0x? Siehe Martinhos Antwort (die mich zugegebenermaßen überrascht hat).

Wie wäre es mit bestimmten Implementierungen vor C ++ 0x? Nun, hier ist zum Beispiel der Quellcode für basic_streambuf<...>:sputcaus GCC 4.5.2 ("streambuf" -Header):

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

Dies führt eindeutig zu keiner Verriegelung. Und das tut es auch nicht xsputn. Und dies ist definitiv die Art von Streambuf, die Cout verwendet.

Soweit ich das beurteilen kann, führt libstdc ++ keine Sperren für die Stream-Operationen durch. Und ich würde keine erwarten, da das langsam sein würde.

Bei dieser Implementierung ist es offensichtlich möglich, dass sich die Ausgabe von zwei Threads gegenseitig beschädigt ( nicht nur verschachtelt).

Könnte dieser Code die Datenstruktur selbst beschädigen? Die Antwort hängt von den möglichen Wechselwirkungen dieser Funktionen ab. Beispiel: Was passiert, wenn ein Thread versucht, den Puffer zu leeren, während ein anderer versucht, aufzurufen, xsputnoder was auch immer. Dies hängt möglicherweise davon ab, wie Ihr Compiler und Ihre CPU die Speicherladevorgänge und -speicher neu anordnen. Es würde eine sorgfältige Analyse erfordern, um sicher zu sein. Es hängt auch davon ab, was Ihre CPU tut, wenn zwei Threads versuchen, denselben Speicherort gleichzeitig zu ändern.

Mit anderen Worten, selbst wenn es in Ihrer aktuellen Umgebung einwandfrei funktioniert, kann es beim Aktualisieren Ihrer Laufzeit, Ihres Compilers oder Ihrer CPU zu Problemen kommen.

Zusammenfassung: "Ich würde nicht". Erstellen Sie eine Protokollierungsklasse, die ordnungsgemäß sperrt, oder wechseln Sie zu C ++ 0x.

Als schwache Alternative könnten Sie cout auf ungepuffert setzen. Es ist wahrscheinlich (obwohl nicht garantiert), dass die gesamte Logik in Bezug auf den Puffer übersprungen und writedirekt aufgerufen wird. Obwohl das unerschwinglich langsam sein könnte.

Nemo
quelle
1
Gute Antwort, aber schauen Sie sich Martinhos Antwort an, die zeigt, dass C ++ 11 die Synchronisation für definiert cout.
edA-qa mort-ora-y
7

Der C ++ - Standard legt nicht fest, ob das Schreiben in Streams threadsicher ist, normalerweise jedoch nicht.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

und außerdem: Sind Standardausgabestreams in C ++ threadsicher (cout, cerr, clog)?

AKTUALISIEREN

Schauen Sie sich die Antwort von @Martinho Fernandes an, um zu erfahren, was der neue Standard C ++ 11 dazu sagt.

Phoxis
quelle
3
Ich denke, da C ++ 11 jetzt der Standard ist, ist diese Antwort jetzt tatsächlich falsch.
edA-qa mort-ora-y
6

Wie andere Antworten erwähnen, ist dies definitiv herstellerspezifisch, da der C ++ - Standard das Threading nicht erwähnt (dies ändert sich in C ++ 0x).

GCC macht nicht viele Versprechungen in Bezug auf Thread-Sicherheit und E / A. Aber die Dokumentation für das, was es verspricht, ist hier:

Das Schlüsselmaterial ist wahrscheinlich:

Der Typ __basic_file ist einfach eine Sammlung kleiner Wrapper um die C stdio-Ebene (siehe auch den Link unter Struktur). Wir sperren uns nicht ein, sondern gehen einfach zu Anrufen zum Öffnen, Schreiben und so weiter über.

Für 3.0 muss die Frage "Ist Multithreading für E / A sicher" mit "Ist die C-Bibliothek Ihrer Plattform für E / A sicher?" Beantwortet werden? Einige sind standardmäßig, andere nicht; Viele bieten mehrere Implementierungen der C-Bibliothek mit unterschiedlichen Kompromissen zwischen Thread-Sicherheit und Effizienz. Sie als Programmierer müssen sich immer um mehrere Threads kümmern.

(Der POSIX-Standard verlangt beispielsweise, dass C stdio FILE * -Operationen atomar sind. POSIX-konforme C-Bibliotheken (z. B. unter Solaris und GNU / Linux) verfügen über einen internen Mutex, um Operationen auf FILE * s zu serialisieren. Sie benötigen jedoch noch keine dummen Dinge wie das Aufrufen von fclose (fs) in einem Thread zu tun, gefolgt von einem Zugriff von fs in einem anderen.)

Wenn die C-Bibliothek Ihrer Plattform threadsicher ist, sind Ihre fstream-E / A-Vorgänge auf der niedrigsten Ebene threadsicher. Für übergeordnete Vorgänge, z. B. das Bearbeiten der in den Stream-Formatierungsklassen enthaltenen Daten (z. B. das Einrichten von Rückrufen in einem std :: ofstream), müssen Sie solche Zugriffe wie jede andere wichtige gemeinsam genutzte Ressource schützen.

Ich weiß nicht, ob sich seit dem genannten 3.0-Zeitrahmen etwas geändert hat.

Die Dokumentation zur Thread-Sicherheit von MSVC für iostreamsfinden Sie hier: http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx :

Ein einzelnes Objekt ist threadsicher zum Lesen aus mehreren Threads. Wenn beispielsweise ein Objekt A gegeben ist, ist es sicher, A gleichzeitig aus Thread 1 und aus Thread 2 zu lesen.

Wenn ein einzelnes Objekt von einem Thread beschrieben wird, müssen alle Lese- und Schreibvorgänge in diesem Objekt auf demselben oder einem anderen Thread geschützt werden. Wenn beispielsweise bei einem Objekt A Thread 1 in A schreibt, muss verhindert werden, dass Thread 2 von A liest oder in A schreibt.

Es ist sicher, in eine Instanz eines Typs zu lesen und zu schreiben, selbst wenn ein anderer Thread in eine andere Instanz desselben Typs liest oder schreibt. Wenn beispielsweise Objekte A und B des gleichen Typs gegeben sind, ist es sicher, wenn A in Thread 1 geschrieben und B in Thread 2 gelesen wird.

...

iostream Klassen

Die iostream-Klassen folgen mit einer Ausnahme denselben Regeln wie die anderen Klassen. Es ist sicher, aus mehreren Threads in ein Objekt zu schreiben. Beispielsweise kann Thread 1 gleichzeitig mit Thread 2 in cout schreiben. Dies kann jedoch dazu führen, dass die Ausgabe der beiden Threads gemischt wird.

Hinweis: Das Lesen aus einem Stream-Puffer wird nicht als Lesevorgang betrachtet. Es sollte als Schreiboperation betrachtet werden, da dies den Status der Klasse ändert.

Beachten Sie, dass diese Informationen für die neueste Version von MSVC gelten (derzeit für VS 2010 / MSVC 10 / cl.exe16.x). Sie können die Informationen für ältere Versionen von MSVC mithilfe eines Dropdown-Steuerelements auf der Seite auswählen (und die Informationen sind für ältere Versionen unterschiedlich).

Michael Burr
quelle
1
"Ich weiß nicht, ob sich seit dem genannten 3.0-Zeitrahmen etwas geändert hat." Das hat es definitiv getan. In den letzten Jahren hat die Implementierung von g ++ - Streams eine eigene Pufferung durchgeführt.
Nemo