coutは同期/スレッドセーフですか?


112

一般に、ストリームが同期されていないと想定しています。適切なロックを行うかどうかはユーザー次第です。しかし、cout標準ライブラリで特別な扱いを受けるようなことはありますか?

つまり、複数のスレッドが書き込みを行っている場合coutcoutオブジェクトを破壊できますか?同期しても、ランダムにインターリーブされた出力が得られることは理解していますが、インターリーブは保証されています。つまりcout、複数のスレッドから使用しても安全ですか?

このベンダーに依存していますか?gccは何をしますか?


重要:ある種の証明が必要なので、「はい」と答えた場合は、回答の参照を提供してください。

私の懸念は、基礎となるシステムコールについても関係ありませんが、それらは問題ありませんが、ストリームによってバッファ層が追加されます。


2
これはベンダーに依存します。C ++(C ++ 0xより前)には、複数のスレッドの概念はありません。
Sven

2
c ++ 0xはどうですか?それはメモリモデルとスレッドとは何かを定義しているので、おそらくこれらのことは出力に流れ落ちましたか?
rubenvb

2
スレッドセーフにするベンダーはありますか?
edA-qa mort-ora-y 2011

誰かが最新のC ++ 2011提案標準へのリンクを持っていますか?
edA-qa mort-ora-y 2011

4
ある意味で、これは、完全な出力が1つのショットで書き込まれるときにprintf光る場所stdoutです。std::cout式チェーンの各リンクを使用すると、に別々に出力されstdoutます。その間に、他のスレッドが書き込みを行っている可能性があります。stdoutこれにより、最終的な出力の順序がめちゃくちゃになります。
legends2k

回答:


106

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)。[注:インターリーブされた文字を避けたい場合は、複数のスレッドによるこれらのオブジェクトとストリームの同時使用を同期する必要があります。—エンドノート]

したがって、破損したストリームは取得されませんが、出力を不要なものにしたくない場合は、手動で同期する必要があります。


2
技術的にはC ++ 98 / C ++ 03には当てはまりますが、誰もが知っていると思います。しかし、これは2つの興味深い質問に答えません:C ++ 0xはどうですか?典型的な実装は実際に何をしますか?
ニモ

1
@ edA-qa mort-ora-y:いいえ、間違っています。C ++ 11は、標準のストリームオブジェクトデフォルトであるのではなく、標準ストリームオブジェクト同期され、明確に定義された動作を維持できることを明確に定義しています。
ildjarn、2011年

12
@ildjarn-いいえ、@ edA-qa mort-ora-yは正しいです。cout.sync_with_stdio()正しい限り、cout追加の同期なしで複数のスレッドから文字を出力するための使用は明確に定義されていますが、個々のバイトのレベルでのみです。したがって、cout << "ab";そしてcout << "cd"別のスレッドで実行される出力してもよいacdb、例えば、が、未定義の動作を引き起こさないかもしれません。
JohannesD

4
@JohannesD:合意に達しています-基盤となるC APIと同期しています。私のポイントは、それが有効な方法で「同期」されていないことです。つまり、ガベージデータが必要ない場合は、手動で同期する必要があります。
ildjarn、2011年

2
@ildjarn、私はゴミデータで大丈夫です、そのビットは私が理解しています。データの競合状態に興味があるだけです。これは、現在は明らかなようです。
edA-qa mort-ora-y 2011

16

これは素晴らしい質問です。

まず、C ++ 98 / C ++ 03には「スレッド」の概念がありません。したがって、その世界では、問題は無意味です。

C ++ 0xはどうですか?Martinhoの答えを見てください(私が驚いたことは認めます)。

C ++ 0x以前の特定の実装はどうですか?たとえば、basic_streambuf<...>:sputcGCC 4.5.2( "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つのスレッドの出力が(単にインターリーブするだけでなく)互いに破壊する可能性があることは明らかです。

このコードはデータ構造自体を破壊する可能性がありますか?答えは、これらの機能の可能な相互作用に依存します。たとえば、あるスレッドがバッファをフラッシュしようとしたときに、別のスレッドが呼び出しを試みた場合、どうなるかxsputnなどです。これは、コンパイラとCPUがメモリのロードとストアを並べ替える方法を決定する方法に依存する場合があります。確かに注意深い分析が必要です。2つのスレッドが同じ場所を同時に変更しようとした場合のCPUの動作にも依存します。

つまり、現在の環境で正常に機能しても、ランタイム、コンパイラー、またはCPUのいずれかを更新すると破損する可能性があります。

エグゼクティブサマリー:「そうしない」適切なロックを行うロギングクラスを構築するか、C ++ 0xに移動します。

弱い代替手段として、coutをunbufferedに設定できます。バッファに関連するすべてのロジックをスキップしてwrite直接呼び出す可能性があります(保証はされません)。それは法外に遅いかもしれませんが。


1
良い答えですが、C ++ 11がの同期を定義していることを示すMartinhoの応答を見てくださいcout
edA-qa mort-ora-y 2011

7

C ++標準では、ストリームへの書き込みがスレッドセーフかどうかは指定されていませんが、通常はスレッドセーフではありません。

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

また、C ++の標準出力ストリームはスレッドセーフですか(cout、cerr、clog)?

更新

@Martinho Fernandesの回答を見て、新しい標準C ++ 11がこれについて何を言っているかを確認してください。


3
C ++ 11が標準になったので、この答えは実際には間違っています。
edA-qa mort-ora-y 2011

6

他の回答が言及しているように、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の時間枠が変更されたかどうかはわかりません。

MSVCのスレッドセーフティのドキュメントは、次の場所にあります。http iostreams//msdn.microsoft.com/en-us/library/c9ceah3b.aspx

単一のオブジェクトは、複数のスレッドからの読み取りに対してスレッドセーフです。たとえば、オブジェクトAが指定されている場合、スレッド1とスレッド2から同時にAを読み取っても安全です。

単一のオブジェクトが1つのスレッドによって書き込まれている場合、同じまたは他のスレッド上のそのオブジェクトへのすべての読み取りと書き込みを保護する必要があります。たとえば、オブジェクトAの場合、スレッド1がAに書き込みを行っている場合、スレッド2はAからの読み取りまたはAへの書き込みを禁止する必要があります。

同じタイプの別のインスタンスに対して別のスレッドが読み取りまたは書き込みを行っている場合でも、タイプの1つのインスタンスに対して読み書きしても安全です。たとえば、同じタイプのオブジェクトAとBが与えられた場合、Aがスレッド1で書き込まれ、Bがスレッド2で読み取られる場合は安全です。

...

iostreamクラス

iostreamクラスは、1つの例外を除いて、他のクラスと同じルールに従います。複数のスレッドからオブジェクトに書き込んでも安全です。たとえば、スレッド1はスレッド2と同時にcoutに書き込むことができますが、これにより2つのスレッドからの出力が混在する可能性があります。

注:ストリームバッファーからの読み取りは、読み取り操作とは見なされません。これはクラスの状態を変更するため、書き込み操作と見なす必要があります。

その情報はMSVCの最新バージョン(現在はVS 2010 / MSVC 10 / cl.exe16.x用)に関するものであることに注意してください。ページのドロップダウンコントロールを使用して、古いバージョンのMSVCの情報を選択できます(古いバージョンでは情報が異なります)。


1
「言及された3.0の時間枠が変更されたかどうかはわかりません。」それは間違いなくしました。過去数年間、g ++ストリーム実装は独自のバッファリングを実行してきました。
Nemo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.