yohhoyの日記

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

マルチスレッドprintfデバッグのお供

マルチスレッドプログラムで printf デバッグをする際に、出力文字列が混ざらないよう排他制御を行うコード記述方法に関するメモ。

本記事では排他制御に関するテクニックに限定し、保守性よりも記述の手軽さを重要視している。(Boost.Formatなどログ出力部に関する話題はスコープ外。)

もっとも単純な方式

普通にmutex+lock_guardでログ出力コードを保護する。lock_guardオブジェクト生成とRAIIイディオム用のブロック { } が必要となり、ログ出力部以外のコード記述が煩雑になる。(もちろん std::lock_guard<std::mutex> 型をtypedefすれば多少は短くなる。)

#include <iostream>
#include <mutex>

std::mutex cout_mutex;  // std::cout保護用mutex

// 並行処理で呼び出される関数
void parallel_invoked()
{
  const std::thread::id tid = std::this_thread::get_id();
  int x, y;
  // ...
  {
    // printfデバッグ用にスレッドIDと変数x, yをダンプ
    std::lock_guard<std::mutex> lk(cout_mutex);
    std::cout << tid << ":" << x << "," << y << std::endl;
  }
  // ...
}

インラインロック方式

Boost.勉強会 #8 大阪での[twitter:@wraith13]さんの発表C++ tips 3 カンマ演算子編を見て思いついた方法。lock/unlockを行う一時オブジェクトとカンマ演算子を組み合わせ、1行でlock+ログ出力+unlock処理を行っている。行頭に「autolk(),」を記載するだけなので簡潔なコードとなる。

std::unique_lock<std::mutex> autolk()
{
  // cout_mutexロック済みのunique_lockを(ムーブで)返す
  return std::unique_lock<std::mutex>(cout_mutex);
}

void parallel_invoked()
{
  const std::thread::id tid = std::this_thread::get_id();
  int x, y;
  // ...
  autolk(), std::cout << tid << ":" << x << "," << y << std::endl;
  // ...
}

ロックオブジェクト(unique_lock)の生成に autolk 関数を経由するのは次の理由による。

typedef std::unique_lock<std::mutex> Lock;
Lock(cout_mutex), cout << /*...*/ << endl;    // [A] ill-formed
(Lock(cout_mutex)), cout << /*...*/ << endl;  // [B] OK

最初に思いつくであろうコード[A]では、「Lock型の変数cout_mutexの宣言、変数coutの宣言、続く不正な<<演算子」と解釈されてコンパイルエラーになる。本来の意図通り「引数にcout_mutexをとるLock型の一時オブジェクトの生成」と解釈させるには、コード[B]のように括弧でくくる必要がある。この括弧を毎回忘れずに記述し、かつcout_mutexを指定するのも面倒なため、ロックオブジェクト生成(=lock取得)を関数経由で行っている。

ラムダ方式(おまけ)

C++11ラムダ式を使った記述方法も考えたが、デバッグ用出力コード以外の部分(dump([&]{ ... });)が冗長でさほどメリットを感じない。強いて利点を挙げれば、dump 関数側を変更してログ出力処理を一括制御できることか。

template <class F>
void dump(F f)
{
    std::lock_guard<std::mutex> lk(cout_mutex);
    f();
}

void parallel_invoked()
{
  const std::thread::id tid = std::this_thread::get_id();
  int x, y;
  // ...
  dump([&]{ std::cout << tid << ":" << x << "," << y << std::endl; });
  // ...
}