Singletonの実装について(2)

前回、通常のSingletonではマルチスレッドのとき問題があるという話をしました.
今回は少し詳しく書きます.


一般的なSingletonの実装はこんな感じです(使用言語はC#です).

using System;

public class Singleton
{
  private static Singleton instance = null;

  private Singleton(){}

  public static Singleton createInstance()
  {
    if (instance == null)  // (1)
    {
      instance = new Singleton();  // (2)
    }

    return instance;
  }
}

instanceがnullの状態でスレッド1がcreateInstanceメソッドを呼び出すと、
(1)の条件を満たし(2)の処理を実施します.この処理が無事に完了すれば、
instanceは非nullとなるため、それ以降のスレッドは(1)ですべてはじかれます.


しかし(2)を実行中にスレッド2が(1)に到達すると、その時点ではまだ
instanceはnullのままですから、スレッド2も(2)に入ってしまいます.
これがマルチスレッドで問題となる原因です.


では、どうすればよいのでしょうか?
スレッド1が(1)に到達してからinstanceが生成されるまでの間、
他のスレッドが(1)に入らないよう、排他制御をかける必要があります.
すると、こんな感じになります.

using System;

public class Singleton
{
  private static Singleton instance = null;
  private static object syncRoot = new Object();  // 排他制御用オブジェクト

  private Singleton(){}

  public static Singleton createInstance()
  {
    lock(syncRoot)
    {
      if (instance == null)
      {
        instance = new Singleton();
      }
    }

    return instance;
  }
}

たしかにこれで同時にinstanceを生成しなくなります.
ただこの実装だと、createInstanceメソッドを呼び出すと必ずlockの獲得/解除を
行うことになります.これはパフォーマンス的にとても不利です.


そんなわけで、以下の実装がこの問題の決定打となっています.

using System;

public class Singleton
{
  private static Singleton instance = null;
  private static object syncRoot = new Object();  // 排他制御用オブジェクト

  private Singleton(){}

  public static Singleton createInstance()
  {
    if (instance == null)
    {
      lock(syncRoot)
      {
        if (instance == null)
        {
          instance = new Singleton();
        }
      }
    }

    return instance;
  }
}

この実装には名前がついていて、Double-Checked Lockingパターンといいます.
僕はこの実装を『Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)』という本で始めて知りました.
ちなみにこの本ではこんな風に紹介されています.

以下は、Double-Checked Lockingパターンを理解し、それを味わうためのコードです。実際、このコードにコンピュータ・エンジニアリングの美を感じることが出来るでしょう:

素敵な文章ですね.


この本を買ったのは2年くらい前かな?
結局のところ、この本は難しすぎて自分ではまったく歯が立ちませんでした.
いつかはGenericsもしっかり身につけておきたいと思います(いつになるやら?)