| なんとか頑張って動的な確保なしにスレッドセーフに出来ました。
class MyPool
{
MyPool()
{
value_ = 0;
// いろいろ処理...
}
MyPool(const MyPool& rhs); // コピー禁止
const MyPool& operator =(const MyPool& rhs); // 代入禁止
static const long INIT_NONE = 0; // 未初期化。この値は0であること
static const long INIT_INITIALIZING = 1; // 初期化中(初回のGetInstanceImpl呼び出し中)
static const long INIT_COMPLETED = 2; // 初期化済み
// シンプルなスレッドセーフではないシングルトンインスタンス取得メソッド本体
static inline MyPool& GetInstanceImpl()
{
static MyPool instance;
return instance;
}
public:
// ロックフリーでスレッドセーフなシングルトンインスタンス取得
static MyPool& GetInstance()
{
static volatile long initStat;
if (initStat != INIT_COMPLETED)
{
// CASでアトミックに『未処理状態なら初期化中状態にする』
if (InterlockedCompareExchange(&initStat, INIT_INITIALIZING, INIT_NONE) == INIT_NONE)
{
// ここに入れるのは未処理状態 -> 初期化中状態に変更出来た1スレッドだけです。
MyPool& instance = GetInstanceImpl();
InterlockedExchange(&initStat, INIT_COMPLETED);
return instance;
}
else
{
// ここに入るのは競合で負けて初期化中状態に出来なかったスレッドです。
// 別スレッド(初期化中状態に出来た唯一のスレッド)が初期化を完了させるまで待ちます。
while (initStat != INIT_COMPLETED) {}
}
}
return GetInstanceImpl();
}
};
※このコードはMyPoolのコンストラクターが例外を送出しないことを前提としています。
1.やはりゼロ初期化が競合しないことを前提にしていますが、大丈夫だと判断しました。
根拠は JIS X3014:2003 8.5の6項に『静的記憶期間を持つオブジェクトは、プログラム開始時に、
その他の全ての初期化に先行してゼロ初期化されなければならない』と明記されているからです。
実際にはプログラムロード時にゼロ初期化されていると予想しています。
2.また、動的な確保を無くしたので Visual C++ 2010 に怒られなくなりました。
GetInstanceImpl()はスレッドセーフではないシングルトンオブジェクト取得関数です。
GetInstance()内でCASによってアトミックにステータスの比較と更新を行うことで、GetInstanceImpl()
の初回の呼び出しで競合を起こすことを防いでいます。
これで一応の目的は果たせましたが、もう少し検討から解決としたいと思います。
#実行速度を計ったらシンプルなスレッドセーフではない実装より速いです。不思議?? |