web-dev-qa-db-ja.com

std :: coutをスレッドセーフにする方法

私はマルチスレッドアプリケーションを使用しています。これは、ロックなしでロギングにstd::coutを多用しています。そのような場合、std::coutをスレッドセーフにするロックメカニズムを簡単に追加するにはどうすればよいですか?

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

もっと良い練習はありますか?

32
xmllmx

注:この回答はC++ 20より前なので、個別のバッファリングで _std::osyncstream_ を使用しませんが、代わりにロックを使用します。

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

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

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

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

2つのクラス_synchronized_ostream_および_locked_ostream_を呼び出します。 _sync_cout_が_synchronized_ostream_をラップする_std::cout_のインスタンスである場合、シーケンス

_sync_cout << "Hello, " << name << "!" << std::endl;
_

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

  1. _synchronized_ostream::operator<<_はロックを取得します
  2. _synchronized_ostream::operator<<_は、「Hello」の出力を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_一時的に破壊され、ロックが解除されます
17
MvG

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

あなたが実際にやろうとしていることが、std :: coutがオペレーターと連結するときにストリングを混合することを停止していると仮定しています<<、複数のスレッドにわたって、ストリングごとに複数回。

文字列が文字化けする理由は、演算子に「外部」の競合があるためです<<これにより、このようなことが発生する可能性があります。

//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 sure \n
jumped of 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();

このように、スティングはすでに完全に形成されているため文字化けすることはありません。さらに、ディスパッチする前にストリングを完全に形成することもお勧めします。

25
Nickolas George

一時的なオブジェクトを作成し、デストラクタに保護コードを配置する この質問 で与えられた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としてデータを収集します。昏睡に達するとすぐに、オブジェクトが破棄され、収集されたすべての情報がフラッシュされます。

11
Conchylicultor

C++20以降、std::osyncstreamラッパーを使用できます。

http://en.cppreference.com/w/cpp/io/basic_osyncstream

{
  std::osyncstream bout(std::cout); // synchronized wrapper for std::cout
  bout << "Hello, ";
  bout << "World!";
  bout << std::endl; // flush is noted, but not yet performed
  bout << "and more!\n";
} // characters are transferred and std::cout is flushed

同じ最終宛先バッファー(上記の例ではstd :: cout)に対して行われるすべての出力にデータ競合が発生せず、その最終へのすべての書き込みがある限り、インターリーブや文字化けが発生しないことが保証されます。宛先バッファはstd :: basic_osyncstreamの(おそらく異なる)インスタンスを介して作成されます。

8
lilezek

実行可能なソリューションでは、各スレッドにラインバッファーを使用します。インターリーブされた行が表示される場合がありますが、インターリーブされた文字は表示されません。これをスレッドローカルストレージにアタッチすると、ロック競合の問題も回避できます。次に、行がいっぱいになると(または、必要に応じてフラッシュ時に)、stdoutに書き込みます。もちろん、この最後の操作ではロックを使用する必要があります。このすべてをストリームバッファに詰め込みます。これをstd :: coutと元のストリームバッファの間に置きます。

これで解決されない問題は、フォーマットフラグ(数値の場合はhex/dec/octなど)のようなものであり、ストリームにアタッチされているため、スレッド間で浸透することがあります。ロギングするだけで、重要なデータには使用しないと仮定すると、それは悪いことではありません。特別にフォーマットしない方が便利です。特定の数値に対して16進数の出力が必要な場合は、次のことを試してください。

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();
}

他のフォーマットでも同様のアプローチが機能します。

4
Ulrich Eckhardt

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が出現するたびに検索して、ロックコードの行を追加したくありません。

申し訳ありません。しかし、私はそれが誰かを助けることを望みます。

3
Germinx

Conchylicultorによって提案された答えの行に沿って、ただしstd::ostringstreamから継承しない:


編集:オーバーロードされた演算子の戻り値の型を修正し、std::endlにオーバーロードを追加しました。


編集1:これを拡張して、マルチスレッドプログラムのロギング/デバッグ用の シンプルなヘッダーのみのライブラリ に拡張しました。


#include <iostream>
#include <mutex>
#include <thread>
#include <vector>    
#include <chrono>

static std::mutex mtx_cout;

// Asynchronous output
struct acout
{
        std::unique_lock<std::mutex> lk;
        acout()
            :
              lk(std::unique_lock<std::mutex>(mtx_cout))
        {

        }

        template<typename T>
        acout& operator<<(const T& _t)
        {
            std::cout << _t;
            return *this;
        }

        acout& operator<<(std::ostream& (*fp)(std::ostream&))
        {
            std::cout << fp;
            return *this;
        }
};

int main(void)
{


    std::vector<std::thread> workers_cout;
    std::vector<std::thread> workers_acout;

    size_t worker(0);
    size_t threads(5);


    std::cout << "With std::cout:" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_cout.emplace_back([&]
        {
            std::cout << "\tThis is worker " << ++worker << " in thread "
                      << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_cout)
    {
        w.join();
    }

    worker = 0;

    std::this_thread::sleep_for(std::chrono::seconds(2));

    std::cout << "\nWith acout():" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_acout.emplace_back([&]
        {
            acout() << "\tThis is worker " << ++worker << " in thread "
                    << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_acout)
    {
        w.join();
    }

    return 0;
}

出力:

With std::cout:
        This is worker 1 in thread 139911511856896
        This is worker  This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911486678784
2 in thread     This is worker 5 in thread 139911503464192139911478286080


With acout():
        This is worker 1 in thread 139911478286080
        This is worker 2 in thread 139911486678784
        This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911503464192
        This is worker 5 in thread 139911511856896
1
cantordust

私は古い質問を知っていますが、それは私の問題を大いに助けてくれました。この投稿の回答に基づいてユーティリティクラスを作成しました。結果を共有したいと思います。

C++ 11またはそれ以降のC++バージョンを使用していることを考えると、このクラスは、標準出力ストリームを呼び出す前に文字列を構成して同時実行の問題を回避するためのprintおよびprintln関数を提供します。これらは、テンプレートを使用してさまざまなデータ型を出力する可変関数です。

あなたは私のgithubのプロデューサー/コンシューマー問題での使用をチェックできます: https://github.com/eloiluiz/threadsBar

だから、これが私のコードです:

class Console {
private:
    Console() = default;

    inline static void innerPrint(std::ostream &stream) {}

    template<typename Head, typename... Tail>
    inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
        stream << head;
        innerPrint(stream, tail...);
    }

public:
    template<typename Head, typename... Tail>
    inline static void print(Head const head, Tail const ...tail) {
        // Create a stream buffer
        std::stringbuf buffer;
        std::ostream stream(&buffer);
        // Feed input parameters to the stream object
        innerPrint(stream, head, tail...);
        // Print into console and flush
        std::cout << buffer.str();
    }

    template<typename Head, typename... Tail>
    inline static void println(Head const head, Tail const ...tail) {
        print(head, tail..., "\n");
    }
};
1
eloiluiz

同期に加えて、このソリューションは、ログが書き込まれたスレッドに関する情報を提供します。

[〜#〜] disclaimer [〜#〜]:ログを同期する単純な方法ですが、デバッグのいくつかの小さな使用例に適用できる場合があります。

thread_local int thread_id = -1;
std::atomic<int> thread_count;

struct CurrentThread {

  static void init() {
    if (thread_id == -1) {
      thread_id = thread_count++;
    }
  }

  friend std::ostream &operator<<(std::ostream &os, const CurrentThread &t) {
    os << "[Thread-" << thread_id << "] - ";
    return os;
  }
};

CurrentThread current_thread;
std::mutex io_lock;
#ifdef DEBUG
#define LOG(x) {CurrentThread::init(); std::unique_lock<std::mutex> lk(io_lock); cout << current_thread; x << endl;}
#else
#define LOG(x)
#endif

これはこのように使用できます。

LOG(cout << "Waiting for some event");

そして、それはログ出力を与えます

[Thread-1] - Entering critical section 
[Thread-2] - Waiting on mutex
[Thread-1] - Leaving critical section, unlocking the mutex
0
samvel1024