yohhoyの日記

技術的メモをしていきたい日記

C++11のcoutとスレッド安全性

C++標準ライブラリのI/Oストリームが提供するstd::coutに対して、複数スレッドから同時に出力したときの振る舞いについてメモ。(C++03以前については、処理系依存が自明のため対象としない。)

C++11標準規格によれば、下記が保証される。

  1. 既定の状態 またはstd::sync_with_stdio(true);を呼び出したのち、
  2. coutに対する出力操作*1を、複数スレッドから並行に行ってもデータ競合(data race)を引き起こさない。
#include <iostream>
#include <future>

void dump(const char* s, int v)
{
  std::cout << s << "=" << v << std::endl;
    // 複数スレッドで並行実行されても安全(※)
}

int main()
{
  auto handle = std::async(std::launch::async, dump, "xyz", 42);
    // 別スレッド上でdump("xyz", 42);を呼び出す
  dump("abc", 0);
  handle.wait();  // async()で作成された別スレッドの完了待ち
  return 0;
}

※ ただし「並行アクセスをしてもcoutオブジェクトが壊れない」「指定したデータが標準出力に出力される」ことが保証されるだけであり、標準出力へ実際に出力される順序は不定。上記コードの出力結果の一例としてはxyz=abc=0\n42\nなどが起こりえる。*2

結局のところ、プログラマが意図した書式で出力させるにはmutexなどの同期機構が別途必要。

#include <mutex>
std::mutex cout_guard;

void dump(const char* s, int v)
{
  std::lock_guard<std::mutex> lk(cout_guard);  // 1行出力する処理を保護
  std::cout << s << "=" << v << std::endl;
}
// 標準出力には xyz=42\nabc=0\n または abc=0\nxyz=42\n のいずれかが出力される。

N3337 27.4.1/p4, 27.5.3.4/p1-2より引用。

4 Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. -- end note]

bool sync_with_stdio(bool sync = true);
1 Returns: true if the previous state of the standard iostream objects (27.4) was synchronized and otherwise returns false. The first time it is called, the function returns true.
2 Effects: If any input or output operation has occurred using the standard streams prior to the call, the effect is implementation-defined. Otherwise, called with a false argument, it allows the standard streams to operate independently of the standard C streams.

関連URL

*1:C++標準ライブラリI/Oストリームオブジェクト(cin, cout, cerrなど)、およびC標準ライブラリの標準入出力(stdoutなど)に対する入出力操作

*2:C++標準規格の記述に従えば xaybzc==402\n\n のように、単一operator<<により出力される文字列でも混合される可能性がある。