web-dev-qa-db-ja.com

C ++ 11のダブルチェックロックシングルトン

次のシングルトン実装のデータ競合はありませんか?

_static std::atomic<Tp *> m_instance;
...

static Tp &
instance()
{
    if (!m_instance.load(std::memory_order_relaxed))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_instance.load(std::memory_order_acquire))
        {
            Tp * i = new Tp;
            m_instance.store(i, std::memory_order_release);    
        }    
    }

    return * m_instance.load(std::memory_order_relaxed);
}
_

ロード操作の_std::memory_model_acquire_は不要ですか?ロード操作とストア操作の両方を_std::memory_order_relaxed_に切り替えることで、さらに緩和することはできますか?その場合、_std::mutex_の取得/解放セマンティクスは、その正確性を保証するのに十分ですか、またはコンストラクターのメモリーへの書き込みがリラックスストアの前に行われるようにするために、さらにstd::atomic_thread_fence(std::memory_order_release)も必要です。 ?それでも、フェンスの使用は_memory_order_release_のストアを持つことと同等ですか?

[〜#〜] edit [〜#〜]:Johnの回答のおかげで、データの競合がないはずの次の実装を思いつきました。内部負荷はまったくアトミックではない可能性がありますが、パフォーマンスに影響を与えないように、リラックスした負荷を残すことにしました。取得メモリの順序で常に外部負荷が発生するのと比較して、thread_local機構は、インスタンスへのアクセスのパフォーマンスを約1桁向上させます。

_static Tp &
instance()
{
    static thread_local Tp *instance;

    if (!instance && 
        !(instance = m_instance.load(std::memory_order_acquire)))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!(instance = m_instance.load(std::memory_order_relaxed)))
        {
            instance = new Tp; 
            m_instance.store(instance, std::memory_order_release);    
        }    
    }
    return *instance;
}
_
43
Nicola Bonelli

その実装はnotレースフリーです。シングルトンのアトミックストアは、リリースセマンティクスを使用しますが、一致する取得操作、つまり、ミューテックスによって既に保護されているロード操作とのみ同期します。

ロックスレッドがシングルトンの初期化を完了する前に、外側の緩和された負荷がnull以外のポインタを読み取る可能性があります。

一方、ロックによって保護されている取得は冗長です。別のスレッドのリリースセマンティクスを持つ任意のストアと同期しますが、その時点で(ミューテックスのおかげで)、格納できる可能性のあるスレッドは現在のスレッドのみです。その負荷はアトミックである必要はありません。別のスレッドからストアが発生することはありません。

C++ 0xマルチスレッドに関するAnthonyWilliamsのシリーズ を参照してください。

20
John Calsbeek

これは素晴らしい質問だと思います。JohnCalsbeekが正解です。

ただし、明確にするために、レイジーシングルトンは古典的なマイヤーズシングルトンを使用して実装するのが最適です。 C++ 11で正しいセマンティクスが保証されています。

§6.7.4

...変数の初期化中に制御が宣言に同時に入る場合、同時実行は初期化の完了を待機する必要があります。 .。

コンパイラーが並行コードを積極的に最適化できるという点で、Meyerのシングルトンが推奨されます。 std::mutexのセマンティクスを保持する必要がある場合、コンパイラはさらに制限されます。さらに、マイヤーのシングルトンは2行であり、間違いを犯すことは事実上不可能です。

これは、マイヤーのシングルトンの典型的な例です。シンプルでエレガント、そしてc ++ 03で壊れています。しかし、c ++ 11ではシンプルでエレガント、そして強力です。

class Foo
{
public:
   static Foo& instance( void )
   {
      static Foo s_instance;
      return s_instance;
   }
};
27
deft_code

call_once も参照してください。以前はシングルトンを使用して何かを実行していましたが、返されたオブジェクトを実際には何にも使用していなかった場合は、call_onceの方が優れたソリューションになる可能性があります。通常のシングルトンの場合、couldcall_onceを実行して(グローバル?)変数を設定し、その変数を返します。

簡潔にするために簡略化:

template< class Function, class... Args>
void call_once( std::once_flag& flag, Function&& f, Args&& args...);
  • グループ(同じフラグオブジェクト)の呼び出しにfとして渡された、正確に1つの関数の正確に1つの実行が実行されます。

  • 選択した関数の上記の実行が正常に完了するまで、グループ内の呼び出しは返されません。

7
MaHuJa