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もしっかり身につけておきたいと思います(いつになるやら?)