11

複数のスレッドによるcoutとcerrへの出力の混合アクセスを回避するには、同期する必要があることを理解しています。coutとcerrの両方を使用するプログラムでは、それらを別々にロックするだけで十分ですか?それとも、coutとcerrに同時に書き込むのはまだ安全ではありませんか?

説明の編集:coutとcerrはC++11では「スレッドセーフ」であることを理解しています。私の質問は、coutへの書き込みと異なるスレッドによるcerrへの書き込みが、coutへの2つの書き込みと同じように、互いに干渉する可能性があるかどうかです(インターリーブ入力などが発生します)。

4

6 に答える 6

12

この関数を実行すると:

void f() {
    std::cout << "Hello, " << "world!\n";
}

複数のスレッドから、2 つの文字列の多かれ少なかれランダムなインターリーブが得られ"Hello, "ます"world\n"。これは、次のようにコードを記述した場合と同様に、2 つの関数呼び出しがあるためです。

void f() {
    std::cout << "Hello, ";
    std::cout << "world!\n";
}

そのインターリーブを防ぐには、ロックを追加する必要があります。

std::mutex mtx;
void f() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Hello, " << "world!\n";
}

つまり、インターリーブの問題は とは何の関係もcoutありません。それを使用するコードについてです。テキストを挿入する2つの別個の関数呼び出しがあるため、複数のスレッドが同じコードを同時に実行するのを防止しない限り、関数呼び出し間でスレッドが切り替わる可能性があります。インターリーブ。

ミューテックスはスレッドの切り替えを妨げないことに注意してください。前のコード スニペットでは、2 つのスレッドから同時にの内容を実行することを防ぎます。f()スレッドの 1 つは、他のスレッドが終了するまで待機する必要があります。

に書き込んでいる場合cerr同じ問題があり、2 つのスレッドが同時にこれらの挿入関数呼び出しを行うことがないようにしない限り、インターリーブされた出力が得られます。つまり、両方の関数が同じものを使用する必要があります。ミューテックス:

std::mutex mtx;
void f() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Hello, " << "world!\n";
}

void g() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cerr << "Hello, " << "world!\n";
}
于 2013-02-01T15:23:43.667 に答える
9

C++11 では、C++03 とは異なり、グローバル ストリーム オブジェクト ( coutcincerr、およびclog) への挿入と抽出はスレッド セーフです。手動で同期する必要はありません。ただし、異なるスレッドによって挿入された文字が、出力中に予期せずインターリーブする可能性があります。同様に、複数のスレッドが標準入力から読み取っている場合、どのスレッドがどのトークンを読み取るかは予測できません。

グローバル ストリーム オブジェクトのスレッド セーフは既定で有効になっていますが、ストリーム オブジェクトのメンバー関数を呼び出して引数としてsync_with_stdio渡すことで無効にすることができます。falseその場合、同期を手動で処理する必要があります。

于 2013-02-01T00:27:19.050 に答える
1

ここにはすでにいくつかの答えがあります。要約し、それらの間の相互作用についても説明します。

通常、

std::cout多くのstd::cerr場合、単一のテキスト ストリームに集中するため、それらを共通にロックすると、最も使いやすいプログラムになります。

問題を無視し、coutデフォルトcerrで対応するものにエイリアスを設定すると、 POSIX のようにstdioスレッドセーフであり、標準 I/O 関数 (C++14 §27.4.1/4、C 単独よりも強力な保証) まで使用できます。この関数の選択に固執すると、ガベージ I/O が発生しますが、未定義の動作は発生しません (これは、有用性に関係なく、言語弁護士が「スレッド セーフ」と関連付ける可能性があるものです)。

ただし、標準形式の I/O 関数 (数値の読み取りと書き込みなど) はスレッドセーフですが、形式を変更するマニピュレーター ( std::hex16 進数やstd::setw入力文字列サイズの制限など) はスレッドセーフではないことに注意してください。そのため、一般に、ロックを省略してもまったく安全であるとは考えられません。

それらを個別にロックすることを選択した場合、事態はより複雑になります。

セパレートロック

パフォーマンスのために、ロックcoutcerr個別にロックすることでロックの競合を減らすことができます。それらは別々にバッファリングされ (またはバッファリングされず)、別々のファイルにフラッシュされる場合があります。

デフォルトでは、 「結合」されているため、各操作の前にcerrフラッシュします。coutこれは分離とロックの両方を無効にするため、cerr.tie( nullptr )何かを行う前に呼び出すことを忘れないでください。(同じことが にも当てはまりますがcin、 には当てはまりませんclog。)

からのデカップリングstdio

標準では、 および に対する操作は人種を導入しないと述べてcoutcerrますが、それが正確に意味するものであるとは言えません。ストリーム オブジェクトは特別なものではありません。それらの基礎となるstreambufバッファは.

さらに、この呼び出しstd::ios_base::sync_with_stdioは、標準ストリームの特殊な側面を取り除き、他のストリームと同様にバッファリングできるようにすることを目的としています。標準ではデータ競合への影響は言及されていませんがsync_with_stdio、libstdc++ および libc++ (GCC および Clang)std::basic_streambufクラスの内部をざっと見てみると、これらはアトミック変数を使用していないため、バッファリングに使用すると競合状態が発生する可能性があることがわかります。(一方、libc++sync_with_stdioは実質的に何もしないので、呼び出しても問題ありません。)

ロックに関係なく追加のパフォーマンスが必要な場合sync_with_stdio(false)は、良い考えです。ただし、cerr.tie( nullptr )ロックが分離されている場合は、ロックが必要です。

于 2016-03-15T05:48:37.927 に答える
0

私は次のようなものを使用します:

// Wrap a mutex around cerr so multiple threads don't overlap output
// USAGE:
//     LockedLog() << a << b << c;
// 
class LockedLog {
public:
    LockedLog() { m_mutex.lock(); }
    ~LockedLog() { *m_ostr << std::endl; m_mutex.unlock(); }

    template <class T>
    LockedLog &operator << (const T &msg)
    {
        *m_ostr << msg;
        return *this;
    }

private:
    static std::ostream *m_ostr;
    static std::mutex m_mutex;
};

std::mutex LockedLog::m_mutex;
std::ostream* LockedLog::m_ostr = &std::cerr;
于 2019-02-06T17:01:47.327 に答える