C++ 11でスレッドをテストするための簡単なプログラムを作成しましたが、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
を同時に実行している理由がわかりません。それは私にとても奇妙に縫い合わされています。
mutex
はexec()
関数のローカル変数であるため、スレッドはdifferentmutex
インスタンスを使用しているため、各スレッドは独自の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 );
受け入れられた答えは正しいです。ただし、懸念事項を分離するのは良いことです。
std::cout
_に出力する方法が必要です。以下は、_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
を使用し、単純化されただけでなく(たとえば、mutex
に対して独自のcout
を維持する必要がないなど)、他のすべての型と関数でもcout
を使用でき、すべてが安全に相互運用できます。 print
関数自体がmutex
を維持するようになり、その事実はprint
のすべてのクライアントからカプセル化されます。
この質問 で与えられたNicolásからのトリックを共有しています。これは、Howard Hinnant実装よりもエレガントであることがわかります。一時的なostringstreamオブジェクトを作成し、デストラクタに保護コードを配置するという考え方です。
/** 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
としてデータを収集します。昏睡に達するとすぐに、オブジェクトが破棄され、収集されたすべての情報がフラッシュされます。
保護されたstd::mutex cout_mutex;
出力に使用されるグローバルstd::cout
(ネームスペース内のどこか)を検討できます。必ずstd::lock<std::mutex>
を使用してください(ミューテックスのロックを解除することを忘れず、例外的な安全のために)。