一般的に、ストリームは同期されていないと想定しています。適切なロックを行うのはユーザー次第です。しかし、cout
のようなものは標準ライブラリで特別な扱いを受けますか?
つまり、複数のスレッドがcout
に書き込んでいる場合、cout
オブジェクトを破壊できますか?同期しても、ランダムにインターリーブされた出力が得られることは理解していますが、インターリーブは保証されています。つまり、複数のスレッドからcout
を使用しても安全ですか?
このベンダーは依存していますか? gccは何をしますか?
重要:「はい」と答えた場合は、何らかの証拠を提供する必要があるため、回答の参考文献を提供してください。
私の懸念は、基礎となるシステムコールに関するものでもありません。それらは問題ありませんが、ストリームはバッファリングのレイヤーを追加します。
C++ 03標準は、それについて何も述べていません。何かのスレッドセーフについて保証がない場合は、スレッドセーフではないものとして扱う必要があります。
ここで特に興味深いのは、cout
がバッファリングされているという事実です。 write
(または特定の実装でその効果を達成するもの)の呼び出しが相互に排他的であることが保証されている場合でも、バッファは異なるスレッドで共有される可能性があります。これにより、ストリームの内部状態がすぐに破損します。
そして、バッファへのアクセスがスレッドセーフであることが保証されていても、このコードで何が起こると思いますか?
// in one thread
cout << "The operation took " << result << " seconds.";
// in another thread
cout << "Hello world! Hello " << name << "!";
おそらく、ここの各行は相互排他的に機能する必要があります。しかし、実装はどのようにそれを保証できますか?
C++ 11では、いくつかの保証があります。 FDISは、§27.4.1[iostream.objects.overview]で次のように述べています。
同期(§27.5.3.4)標準iostreamオブジェクトのフォーマットおよび未フォーマットの入力(§27.7.2.1)および出力(§27.7.3.1)関数または複数のスレッドによる標準Cストリームへの同時アクセスは、データの競合(§ 1.10)。 [注:インターリーブされた文字を回避する場合、ユーザーは複数のスレッドによるこれらのオブジェクトとストリームの同時使用を同期する必要があります。 —終了ノート]
そのため、ストリームが破損することはありませんが、出力をガベージにしたくない場合は、手動で同期する必要があります。
これは素晴らしい質問です。
まず、C++ 98/C++ 03には「スレッド」という概念がありません。したがって、その世界では、問題は無意味です。
C++ 0xはどうですか? Martinhoの答え (私は驚きました)を参照してください。
C++ 0x以前の特定の実装はどうですか?さて、たとえば、GCC 4.5.2のbasic_streambuf<...>:sputc
のソースコード(「streambuf」ヘッダー)は次のとおりです。
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;
}
明らかに、これはロックを実行しません。また、xsputn
も同様です。そして、これは間違いなくcoutが使用するstreambufのタイプです。
私が知る限り、libstdc ++はどのストリーム操作もロックしません。そして、それは遅いので、私は何も期待していません。
したがって、この実装では、明らかに2つのスレッドの出力が互いに破損する可能性があります(notインターリーブ)。
このコードはデータ構造自体を破損する可能性がありますか?答えは、これらの機能の可能な相互作用に依存します。たとえば、あるスレッドがバッファをフラッシュしようとしたときに、別のスレッドがxsputn
などを呼び出そうとするとどうなりますか。コンパイラとCPUがメモリのロードとストアを並べ替える方法に依存する場合があります。慎重に分析する必要があります。また、2つのスレッドが同じ場所を同時に変更しようとした場合のCPUの動作にも依存します。
言い換えれば、たとえ現在の環境でうまく動作したとしても、ランタイム、コンパイラー、またはCPUのいずれかを更新すると壊れる可能性があります。
エグゼクティブサマリー:「私はしない」。適切なロックを行うロギングクラスを構築するか、C++ 0xに移行します。
弱い選択肢として、coutをunbufferedに設定できます。 (保証はされませんが)バッファーに関連するすべてのロジックをスキップし、write
を直接呼び出す可能性があります。それは非常に遅いかもしれませんが。
C++標準では、ストリームへの書き込みがスレッドセーフかどうかは指定されていませんが、通常はそうではありません。
www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-plus-thread-safe-logging
また、 C++スレッドセーフ(cout、cerr、clog)の標準出力ストリームはありますか?
[〜#〜] update [〜#〜]
@Martinho Fernandesの答えを見て、新しい標準C++ 11がこれについて何を伝えているかを知ってください。
他の回答が言及しているように、これは間違いなくベンダー固有のものです。C++標準ではスレッド化に言及していないためです(C++ 0xでの変更)。
GCCは、スレッドの安全性とI/Oについて多くの約束をしていません。しかし、それが約束することのドキュメントはここにあります:
主なものはおそらく次のとおりです。
__basic_fileタイプは、単にC stdioレイヤーの小さなラッパーのコレクションです(再び、構造の下のリンクを参照)。自分自身をロックするのではなく、単にfopen、fwriteなどの呼び出しにパススルーします。
したがって、3.0の場合、「マルチスレッドはI/Oに対して安全ですか?」という質問に答える必要があります。「プラットフォームのCライブラリはI/Oに対してスレッドセーフですか?」いくつかはデフォルトであり、いくつかはそうではありません。多くは、スレッド安全性と効率のさまざまなトレードオフを備えたCライブラリの複数の実装を提供します。プログラマーであるあなたは、常に複数のスレッドに注意する必要があります。
(例として、POSIX標準では、C stdioのFILE *操作がアトミックであることが必要です。POSIX準拠のCライブラリ(SolarisおよびGNU/Linuxなど)には、FILE *の操作をシリアル化する内部ミューテックスがあります。あるスレッドでfclose(fs)を呼び出した後、別のスレッドでfsにアクセスするなどの愚かなことをしないでください。)
したがって、プラットフォームのCライブラリがスレッドセーフである場合、fstream I/O操作は最低レベルでスレッドセーフになります。ストリームフォーマットクラスに含まれるデータの操作(std :: ofstream内でコールバックを設定するなど)などの高レベルの操作では、他の重要な共有リソースと同様に、そのようなアクセスを保護する必要があります。
言及されている3.0の時間枠で何か変更があったかどうかはわかりません。
iostreams
に関するMSVCのスレッドセーフティドキュメントは、次の場所にあります。 http://msdn.Microsoft.com/en-us/library/c9ceah3b.aspx :
単一のオブジェクトは、複数のスレッドからの読み取りに対してスレッドセーフです。たとえば、オブジェクトAが与えられた場合、スレッド1とスレッド2から同時にAを読み取ることは安全です。
単一のオブジェクトが1つのスレッドによって書き込まれている場合、同じスレッドまたは他のスレッド上のそのオブジェクトに対するすべての読み取りおよび書き込みを保護する必要があります。たとえば、オブジェクトAが与えられた場合、スレッド1がAに書き込みを行う場合、スレッド2はAからの読み取りまたは書き込みを禁止する必要があります。
別のスレッドが同じ型の別のインスタンスを読み書きしている場合でも、型の1つのインスタンスを読み書きしても安全です。たとえば、同じタイプのオブジェクトAとBが与えられた場合、Aがスレッド1で書き込まれ、Bがスレッド2で読み取られると安全です。
...
iostreamクラス
Iostreamクラスは、1つの例外を除いて、他のクラスと同じ規則に従います。複数のスレッドからオブジェクトに書き込むのは安全です。たとえば、スレッド1はスレッド2と同時にcoutに書き込むことができます。ただし、これにより、2つのスレッドからの出力が混在する可能性があります。
注:ストリームバッファーからの読み取りは、読み取り操作とは見なされません。これはクラスの状態を変更するため、書き込み操作と見なす必要があります。
この情報はMSVCの最新バージョンに関するものであることに注意してください(現在はVS 2010/MSVC 10/cl.exe
16.x)。ページ上のドロップダウンコントロールを使用して、MSVCの古いバージョンの情報を選択できます(古いバージョンでは情報が異なります)。