2013-08-16 16 views
8

スレッドをC++ 11でテストするための簡単なプログラムを書いていますが、std::coutは期待どおり動作しません。複数のスレッドでstd :: coutを使用する

class Printer 
{ 
public: 
    void exec() 
    { 
     mutex m; 
     m.lock(); 
     cout<<"Hello "<<this_thread::get_id()<<endl; 
     chrono::milliseconds duration(100); 
     this_thread::sleep_for(duration); 
     m.unlock(); 

    } 
}; 

int main() 
{ 
    Printer printer; 

    thread firstThread([&printer](){ 
     while(1) 
      printer.exec(); 

    }); 
    thread secondThread([&printer](){ 
     while(1) 
      printer.exec(); 
    }); 

    firstThread.join(); 
    secondThread.join();  
} 

結果の一部:

Hello 11376 
Hello 16076 
Hello 16076 
Hello Hello 11376 
16076 
Hello 11376 
,.... 

私は2つのスレッドが同時にstd::coutを実行している理由を私は理解して傾けるようにスレッドをロックするミューテックスを使用します。 それは私に非常にうまくつながっています。何が起こっているのか説明できますか?

+1

答えに含まれるソリューションを実装したら、手動でロック/アンロックする代わりに 'lock_guard'を使うべきです。そして、(例えば、 'endl'で)フラッシングを気にしないでください。 '\ n'を使用してください。 – bames53

答えて

21

スレッドがmutexexec()関数内のローカル変数であり、各スレッドは、スレッド間で同期して得られた独自mutexをロックするようにmutexをロックすることは無意味である異なるmutexインスタンスを使用しています。同じmutexインスタンスは、同期を達成するためにスレッドによって使用されなければなりません。

投稿コードを修正するには、mutexをメンバー変数にします。しかし、別のPrinterオブジェクトが作成された場合、別のPrinterインスタンスを使用するスレッド間で同期はありません。この場合、mutexは、同期を確保するためにstaticメンバ変数である必要があります:

class Printer 
{ 
public: 
    //... 
private: 
    static std::mutex mtx_; 
}; 

std::mutex Printer::mtx_; 

mutexが常に解放されていることを確認するには、関係なく、機能が正常に終了するか、例外を介した場合、std:lock_guardを使用します。

std::lock_guard<std::mutex> lock(m); // 'm' locked, and will be 
            // unlocked when 'lock' is destroyed. 
std::cout<< "Hello " << std::this_thread::get_id() << std::endl; 
std::chrono::milliseconds duration(100); 
std::this_thread::sleep_for(duration); 
+0

'mutex'を' static'保存期間で宣言するのが修正されました。 ... またはその。 – Casey

+0

@Casey、またはスレッドが同じ 'Printer'インスタンスを使用しているためのメンバ変数です。 – hmjd

+1

@hjmdこの場合、 'std :: cout'のスコープに対応するために' static'が好きです。そうすれば、メンテナンスプログラマーの中には、他の機能の中で別の 'Printer'が作成されているときに作業を続けることができます。 – Casey

1

あなたは保護さstd::cout出力に使用され、グローバルstd::mutex cout_mutex;(あなたの名前空間のどこか)を検討することができます。 std::lock<std::mutex>を使用していることを確認してください(ミューテックスのロックを解除し、例外の安全性を忘れることはできません)。

11

回答は正しいです。しかし、懸念を分けるのが良いです:

  1. std::coutにスレッドセーフな方法で印刷する必要があります。
  2. スレッドで実行して起動するには、オブジェクト/関数/関数を作成する必要があります。ここで

だけstd::coutに引数を収集し、static std::mutexの下でそれらをストリーミングに集中することを私が使用するユーティリティです:

#include <iostream> 
#include <mutex> 

std::ostream& 
print_one(std::ostream& os) 
{ 
    return os; 
} 

template <class A0, class ...Args> 
std::ostream& 
print_one(std::ostream& os, const A0& a0, const Args& ...args) 
{ 
    os << a0; 
    return print_one(os, args...); 
} 

template <class ...Args> 
std::ostream& 
print(std::ostream& os, const Args& ...args) 
{ 
    return print_one(os, args...); 
} 

std::mutex& 
get_cout_mutex() 
{ 
    static std::mutex m; 
    return m; 
} 

template <class ...Args> 
std::ostream& 
print(const Args& ...args) 
{ 
    std::lock_guard<std::mutex> _(get_cout_mutex()); 
    return print(std::cout, args...); 
} 

このコードはstd::cout以外のストリームのために再利用が、上記であることができますターゲットはstd::coutです。これであなたのPrinter::exec()は今、大幅に簡略化することができます。

void exec() 
{ 
    print("Hello ", std::this_thread::get_id(), '\n'); 
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
} 

さて、あなただけのPrinterスレッドセーフな方法で使用cout、とは、簡素化されていますない(例えば、coutの場合はmutexを維持する必要はありません)。他のすべてのタイプと機能もすべてcoutを使用して相互に安全に相互運用できます。 print関数自体がmutexを保持し、その事実はprintのクライアントからすべてカプセル化されています。

+5

printはテンプレート関数です。私はあなたが複数のミューテックスで終わることができると思います。 –

+1

@JanChristophUhde:優れた観察!誰かが気づくのに3年しかかかりませんでした! :-) 一定。ありがとう。 –

3

私は、ニコラスからthis questionで与えられたトリックを共有しています。これは、Howard Hinnantの実装よりも洗練されています。アイデアは、一時的なストリームストリームオブジェクトを作成し、保護コードをデストラクタに配置することです。

/** 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{} << "val = " << 33 << std::endl; 

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

関連する問題