C++20標準ライブラリでは「同期出力ストリーム(synchronized output stream)」が追加されました。これによって並行実行される複数スレッドから 行単位のストリーム同時出力 を、安全かつ手軽に行えるようになります。1
#include <iostream>
#include <mutex>
// std::cout出力を保護するミューテックスを用意...
std::mutex g_cout_mutex;
// 複数スレッドからworker_thread関数が同時に呼び出されると仮定
void worker_thread(int id)
{
{
// 文字列"Worker#<N>"が1行で出力されることを保障するため
// ミューテックスg_cout_mutexによる排他制御が必須!!
std::lock_guard lk{g_cout_mutex};
std::cout << "Worker#" << id << std::endl;
}
// ...
}
std::osyncstreamクラス
下記の例示ソースコードでは新ヘッダ<syncstream>
をincludeし、標準出力ストリームstd::cout
をstd::osyncstream
でラップしています。
#include <iostream>
#include <syncstream> // NEW
// 複数スレッドからworker_thread関数が同時に呼び出されると仮定
void worker_thread(int id)
{
// 文字列"Worker#<N>"は他スレッド出力と混じることなく1行で出力される
std::osyncstream{std::cout} << "Worker#" << id << std::endl;
{
// syncoutへのストリーム出力は一旦バッファリングされ...
std::osyncstream syncout{std::cout};
syncout << "Worker#" << id << " ";
for (int n = 0; n < id; n++) {
syncout << (n ? "," : "[") << n;
}
syncout << "]" << std::endl;
} // syncoutオブジェクト破棄時にstd::coutへアトミックに送出(emit)される
}
std::osyncstream
クラスでは、スレッド間の排他制御は「ラップ対象の出力ストリーム(先例ではstd::cout
)」に対して行われます。つまりストリーム出力操作(operetor<<
)を行う std::osyncstream
オブジェクト自体は異なっていてもよく、実際にストリーム出力を行う箇所で一時オブジェクトの生成/破棄を行えば十分です。
合わせて関連するマニピューレータ(emit_on_flush
,noemit_on_flush
,flush_emit
)も追加され、より細かい制御も可能となっています。これらの機能詳細は記事「C++ Synchronized Buffered Ostream」でも紹介しています。
利用上の注意(2020/12/1現在)
2020/12/1現在、主要C++コンパイラGCC/Clang/MSVCいずれにおいても 標準ヘッダ<syncstream>
は未実装 です。この便利機能を試すことはできません。どうして…
先行して試したい方向けに、提案時の参照実装を紹介しておきます。
https://github.com/PeterSommerlad/SC22WG21_Papers/tree/master/workspace/p0053_basic_osyncstreambuf
参考ページ
- https://timsong-cpp.github.io/cppwp/n4861/syncstream
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0053r6.pdf
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0753r2.pdf
-
そのまま
std::cout
出力すればいいんじゃね?何がダメなの?という方は記事「スレッドセーフという幻想と現実」や「C++11のcoutとスレッド安全性」をご一読ください。 ↩