46

私はマルチスレッド アプリケーションを持っています。これはstd::cout、ロックなしでログを大量に使用します。このような場合、ロック機構を簡単に追加してstd::coutスレッドセーフにする方法を教えてください。

std::cout発生するたびに検索して、ロックコードの行を追加したくありません。それは面倒すぎる。

より良い練習はありますか?

4

11 に答える 11

42

これがすべてのコンパイラ/標準ライブラリのバージョンに当てはまるかどうかはわかりませんが、使用しているコードベースstd::cout::operator<<()ではすでにスレッドセーフです。

複数のスレッドにわたって、文字列ごとに複数回連結するときに、実際にやろうとしていることがstd::cout文字列の混合を停止すると仮定しています。operator<<

文字列が文字化けする理由は、これに「外部」レースがあるためoperator<< です。このようなことが起こる可能性があります。

//Thread 1
std::cout << "the quick brown fox " << "jumped over the lazy dog " << std::endl;

//Thread 2
std::cout << "my mother washes" << " seashells by the sea shore" << std::endl;

//Could just as easily print like this or any other crazy order.
my mother washes the quick brown fox seashells by the sea shore \n
jumped over the lazy dog \n

その場合、独自のスレッド セーフな cout を作成したり、cout で使用するロックを実装したりするよりも、はるかに簡単な答えがあります。

cout に渡す前に文字列を作成するだけです

例えば。

//There are other ways, but stringstream uses << just like cout.. 
std::stringstream msg;
msg << "Error:" << Err_num << ", " << ErrorString( Err_num ) << "\n"; 
std::cout << msg.str();

この方法では、文字列はすでに完全に形成されているため文字化けすることはありません。さらに、文字列をディスパッチする前に文字列を完全に形成することをお勧めします。

于 2014-11-13T12:46:31.067 に答える
23

注:この回答はC ++ 20より前のものであるため、個別のバッファリングでは使用されませんstd::osyncstreamが、代わりにロックが使用されます。

coutミューテックスをラップして関連付ける独自のクラスを実装できると思います。そのoperator <<新しいクラスのは、次の3つのことを行います。

  1. ミューテックスのロックを作成し、他のスレッドをブロックする可能性があります
  2. 出力を実行します。つまり<<、ラップされたストリームと渡された引数の演算子を実行します
  3. のクラスのインスタンスを作成し、それにロックを渡します

この異なるクラスは、ロックを保持<<し、ラップされたストリームにオペレーターを委任します。その2番目のクラスのデストラクタは、最終的にロックを破棄し、ミューテックスを解放します。

したがって、単一のステートメントとして、つまり単一の<<呼び出しシーケンスとして記述した出力は、すべての出力が同じミューテックスでそのオブジェクトを通過する限り、アトミックに出力されます。

synchronized_ostream2つのクラスをとと呼びましょうlocked_ostream。がラップsync_coutするインスタンスの場合、シーケンスsynchronized_ostreamstd::cout

sync_cout << "Hello, " << name << "!" << std::endl;

次のアクションが発生します。

  1. synchronized_ostream::operator<<ロックを取得します
  2. synchronized_ostream::operator<<「こんにちは」の印刷をに委任しますcout
  3. operator<<(std::ostream&, const char*)「こんにちは」と表示されます
  4. synchronized_ostream::operator<<を構築し、locked_ostreamそれにロックを渡します
  5. locked_ostream::operator<<の印刷を委任しnameますcout
  6. operator<<(std::ostream&, std::string)名前を印刷します
  7. cout感嘆符とエンドラインマニピュレータで同じ委任が発生します
  8. 一時的に破壊されlocked_ostream、ロックが解除されます
于 2013-02-05T23:23:57.433 に答える
17

一時オブジェクトを作成し、デストラクタに保護コードを配置するというこの質問で与えられた Nicolás のトリックが本当に気に入っています。

/** Thread safe cout class
  * Exemple of use:
  *    PrintThread{} << "Hello world!" << std::endl;
  */
class PrintThread: public std::ostringstream
{
public:
    PrintThread() = default;

    ~PrintThread()
    {
        std::lock_guard<std::mutex> guard(_mutexPrint);
        std::cout << this->str();
    }

private:
    static std::mutex _mutexPrint;
};

std::mutex PrintThread::_mutexPrint{};

std::coutその後、任意のスレッドから通常の として使用できます。

PrintThread{} << "my_val=" << val << std::endl;

オブジェクトは定期的にデータを収集しますostringstream。昏睡状態に達するとすぐに、オブジェクトは破壊され、収集されたすべての情報がフラッシュされます。

于 2017-01-11T23:47:08.533 に答える
5

C++11 アプリケーションを高速にデバッグし、インターリーブ出力を回避するために、次のような小さな関数を記述します。

...
#include <mutex>
...
mutex m_screen;
...
void msg(char const * const message);
...
void msg(char const * const message)
{
  m_screen.lock();
  cout << message << endl;
  m_screen.unlock();
}

これらのタイプの関数を出力に使用します。数値が必要な場合は、次のようなものを使用します。

void msgInt(char const * const message, int const &value);
...
void msgInt(char const * const message, int const &value)
{
  m_screen.lock();
  cout << message << " = " << value << endl;
  m_screen.unlock();
}

これは簡単で、うまく機能しますが、技術的に正しいかどうかはわかりません。ですので、ご意見をお聞かせいただければ幸いです。


さて、私はこれを読んでいませんでした:

std::cout が出現するたびに検索して、ロックコードの行を追加したくありません。

ごめんなさい。しかし、それが誰かに役立つことを願っています。

于 2015-03-14T12:50:01.587 に答える
4

A feasible solution uses a line-buffer for each thread. You might get interleaved lines, but not interleaved characters. If you attach that to thread-local storage, you also avoid lock contention issues. Then, when a line is full (or on flush, if you want), you write it to stdout. This last operation of course has to use a lock. You stuff all this into a streambuffer, which you put between std::cout and it's original streambuffer.

The problem this doesn't solve is things like format flags (e.g. hex/dec/oct for numbers), which can sometimes percolate between threads, because they are attached to the stream. It's nothing bad, assuming you're only logging and not using it for important data. It helps to just not format things specially. If you need hex output for certain numbers, try this:

template<typename integer_type>
std::string hex(integer_type v)
{
    /* Notes:
    1. using showbase would still not show the 0x for a zero
    2. using (v + 0) converts an unsigned char to a type
       that is recognized as integer instead of as character */
    std::stringstream s;
    s << "0x" << std::setfill('0') << std::hex
        << std::setw(2 * sizeof v) << (v + 0);
    return s.str();
}

Similar approaches work for other formats as well.

于 2013-02-06T21:52:03.197 に答える
0

私はあなたと同様の問題を抱えていました。次のクラスを使用できます。これは への出力のみをサポートしstd::coutますが、一般的なものが必要な場合はお知らせください。以下のコードでtsprintは、クラスのインライン一時オブジェクトを作成しますThreadSafePrinter。の代わりにを使用している場合は、必要tsprintに応じてに変更できます。そのため、 のインスタンスを置き換える必要はありませんが、一般的にはこのような方法はお勧めしません。いずれにしても、プロジェクトの最初からこのようなデバッグ行に特別な出力シンボルを使用することをお勧めします。coutcoutstd::coutcout

私もこの解決策が好きです:1。私のソリューションでは、すべてのスレッドが対応する静的オブジェクトへの挿入を続行thread_local stringstreamし、デストラクタでトリガーされるフラッシュが必要な場合にのみミューテックスをロックできます。これにより、ミューテックスロックが保持される期間が短縮され、効率が向上することが期待されます。1sync_endlで述べた解決策と同様のメカニズムを含めることができるかもしれません。

class ThreadSafePrinter
{
    static mutex m;
    static thread_local stringstream ss;
public:
    ThreadSafePrinter() = default;
    ~ThreadSafePrinter()
    {
        lock_guard  lg(m);
        std::cout << ss.str();
        ss.clear();
    }

    template<typename T>
    ThreadSafePrinter& operator << (const T& c)
    {
        ss << c;
        return *this;
    }


    // this is the type of std::cout
    typedef std::basic_ostream<char, std::char_traits<char> > CoutType;

    // this is the function signature of std::endl
    typedef CoutType& (*StandardEndLine)(CoutType&);

    // define an operator<< to take in std::endl
    ThreadSafePrinter& operator<<(StandardEndLine manip)
    {
        manip(ss);
        return *this;
    }
};
mutex ThreadSafePrinter::m;
thread_local stringstream ThreadSafePrinter::ss;
#define tsprint ThreadSafePrinter()

void main()
{
    tsprint << "asd ";
    tsprint << "dfg";
}
于 2021-11-08T14:05:23.790 に答える