memologue RSSフィード

書いている人

日記というよりは備忘録、ソフトウェア技術者の不定期メモ。あるいはバッドノウハウ集。プライベートで調査した細々した諸々のスナップショット。嘘が散りばめられています。ISO/IEC 14882(C++)とPOSIX, GCC, glibc, ELFの話ばかりで、WindowsやMacの話はありません。特に記載がなければLinux/x86とILP32が前提です。時間の経過と共に古い記事は埋もれてしまいます。検索エンジンから飛んできた場合は、ページ内検索をご利用いただくかgoogleキャッシュを閲覧してみてください。技術的な記事を書きためる場所として使っています。言及してもらえると喜びます。主要な記事の一覧を書いておきます:

にんきコンテンツ: [GCC] mainを一度も呼ばないばかりか蹂躙する / Binary2.0 Conference 2006 発表資料 / C++ で SICP / ついカッとなって実行バイナリにパッチ / hogetrace - 関数コールトレーサ

昔のPOSIX関係の記事: シグナルの送受に依存しない設計にしよう / シグナルハンドラで行ってよい処理を知ろう / マルチスレッドのプログラムでのforkはやめよう / スレッドの「非同期キャンセル」を行わない設計にしよう / スレッドの「遅延キャンセル」も出来る限り避けて通ろう / マルチスレッドプログラミングの「常識」を守ろう / C++でsynchronized methodを書くのは難しい / シグナルハンドラからのforkするのは安全か? / g++ の -fthreadsafe-statics ってオプション知ってます? / 非同期シグナルとanti-pattern / localtimeやstrtokは本当にスレッドセーフにできないのか / UNIXの規格について / マルチスレッドと共有変数 - volatile?なにそれ。 / type punning と strict aliasing / アセンブラで遊ぶ時に便利なgdb設定 / 最近の記事は一覧から

2004-07-13

[] C++でsynchronized methodを書くのは難しい (1)

Javaにはsynchronizedという便利なキーワードがあります。このキーワードを使うと、例えば次のように簡単にメソッドを「同期化」することができます。同期化されたメソッドは、複数のスレッドで同時に実行されることがありません。

public class Foo {
  ...
  public synchronized boolean getFoo() { ... }

さて、C++ (with pthread) で同様の機能を実現するにはどうしたらよいでしょう?まず、一番単純な方法は次のようなものです。

// 方法 a

void Foo::need_to_sync(void) {
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  pthread_mutex_lock(&mutex);

  // 処理

  pthread_mutex_unlock(&mutex);
  return;
}

この方法は、C言語の場合はともかく、C++言語で用いるには若干問題があります。それは

  • 「処理」の途中でreturn
  • 「処理」の途中で例外が送出

された場合にmutexがunlockされないことです。この点を改良すると、コードは次のようになります。

// 方法 b

class ScopedLock : private boost::noncopyable {
public:
  explicit ScopedLock(pthread_mutex_t& m) : m_(m) {
    pthread_mutex_lock(&m_);
  }
  ~ScopedLock(pthread_mutex_t& m) {
    pthread_mutex_unlock(&m_);
  }
private:
  pthread_mutex_t& m_;
};

void Foo::need_to_sync(void) {
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  { // この括弧はなくてもいいですが。
    ScopedLock lock(mutex);

    // 処理

  }
  return;
}

OK。returnの件と例外の件は解決しました。しかし、上記は完全ではありません。いくつか問題があります。

  1. 素の pthread_mutex_t を使用するのはC++的ではない。特に下記の点が問題:
    • 他のMutex型と同一に扱えない
    • 他のMutex型と同一のScopedLockクラスを用いてロックできない
  2. Javaのsynchronizedメソッドは「再帰ロック可能」であるが、上記コードはそうなっていない。「処理」が自メソッドを再帰呼び出しするとデッドロックしてしまう

特に2.の再帰ロックの問題が重要でしょう。これは、glibc拡張を用いて良いなら次のように解決できます。

// 方法 c

void Foo::need_to_sync(void) {
  static pthread_mutex_t mutex = PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP;

NP*1というサフィックスからわかるように、この方法は移植性がありません。再帰mutexを初期化するには、pthread_mutex_init関数を用いなければなりません。pthread_mutex_init関数は、「一つのスレッドが一回だけ」呼ばなければなりません。これをsynchronized method的手法で実現しようとすると「鶏が先か卵が先か」という話になってしまいますので、pthread_onceという関数を用いて実装するのがSUSv3にも載っている定石です。

// 方法 d

namespace /* anonymous */ {
  pthread_once_t      once = PTHREAD_ONCE_INIT;
  pthread_mutex_t     mutex;
  pthread_mutexattr_t attr;

  void mutex_init() {
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
  }
}

void Foo::need_to_sync(void) {
  pthread_once(&once, mutex_init);
  {
    ScopedLock lock(mutex);

    // 処理

  }
  return;
}

OK。これで再帰ロックの問題は解決できました。しかし…。この方法は

  • 益々C++的ではない。synchronizeしたいメソッド毎にこんな処理を記述するのは非効率すぎる
  • ランタイムコストが大きい。遅い。

という新たな問題を生んでしまいます。


続き

*1:non portable の意

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証