1
template <typename T, typename Lock>
class Singleton
{
public:
static T * getInstance()
{
    if (obj == 0)
    {
        lock.lock();
        if (obj == 0)
        {
            obj = new T;
        }
        lock.unlock();
    }
    return obj;
}

void deleteInstance()
{
   delete obj;
   obj = 0;
}

private:
volatile static T * obj = 0;
static Lock lock; // some sort of mutex
Singleton();
~Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
};

objは揮発性である必要がありますか?最初のスレッドがTインスタンスを作成し、その前に2番目のスレッドがすでにキャッシュに0でobjをロードしているとすると、最新のプロセッサキャッシュは無効になるか、2番目のスレッドがobjの0値を使用してTを作成する可能性があります2回目?両方のスレッドが異なるコアで実行されていると仮定します。

また、シングルトンデータとして静的Tの代わりに静的T*を使用する際に発生する可能性のある問題を教えてください。

4

5 に答える 5

3

このgetInstance()メソッドは、ダブルチェック ロックを使用しています。コンパイラが行のコードを生成する方法によっては、競合状態が発生する可能性があるため、ダブルチェック ロックは本質的に危険です。

obj = new T;

この行には、基本的に次の 3 つの手順が含まれます。

  1. sizeof (T)バイトを割り当てます。
  2. T割り当てられたスペースにオブジェクトを構築します。
  3. に割り当てられたスペースへのポインターを割り当てますobj

問題は、コンパイラがこれらのステップを順番に実行するコードを生成する必要がないことです。たとえば、sizeof (T)バイトを割り当てobj次にを割り当て、その場で構築Tすることができます。Tそのシナリオでは、2 つの異なるスレッドが新しいオブジェクトを構築できるという競合状態があります。Tまた、2 つの異なるスレッドが同じ場所でオブジェクトの構築を試みることもあります。

Scott Meyers と Andrei Alexandrescu によるC++ と Double-Checked Locking の危険性を参照してください。

に関しては、他のスレッドがまだオブジェクトを使用しているdeleteInstance()ときに誤ってオブジェクトを削除するというエラーを発生させる可能性があるため、おそらくそのような関数を提供するべきではありません。T

編集:getInstance()二重チェックのロックを回避する実装は次のとおりです。

static T * getInstance()
{
    lock.lock();
    if (!obj)
    {
        try
        {
            obj = new T;
        }
        catch (...)
        {
            obj = NULL;
            lock.unlock();
            throw;
        }
    }
    lock.unlock();
    return obj;
}
于 2012-08-25T22:04:55.567 に答える
2

getInstance結構ですし、メンバーにする必要はありませんvolatile

ただし、に問題があり、別のスレッドから呼び出された場合deleteInstance、(のチェック後でも)二重削除を実行する可能性があります。NULL

もう1つの問題は、にリセットされdeleteInstanceないため、削除後、新しいオブジェクトを作成するのではなく、ダングリングポインタを返すことです。objNULLgetInstance

于 2012-08-25T22:01:36.477 に答える
2

これは、シングルトンを実装する恐ろしい方法です (シングルトンを実装することは、そもそも疑わしい方法です...)。C++11 の関連部分をサポートするコンパイラを使用している場合は、静的ローカル変数を使用する必要があります。

template<typename T>
T& getInstance() {
    static T instance;
    return instance;
}

それ以外の場合は、Boost.Threadのcall_onceなどを使用する必要があります。

//singleton.hpp
T& getInstance();

//singleton.cpp
static T* singleton;
static boost::once_flag flag=BOOST_ONCE_INIT;

static void initialize() {
    singleton = new T();
}

T& getInstance() {
    boost::call_once(flag, initialize);
    return *singleton;
}

もう 1 つのオプションは、グローバルを明示的に初期化する関数を追加し、新しいスレッドを開始する前にそれを呼び出すことです。

于 2012-08-25T23:45:14.520 に答える
1

変数を揮発性にする必要はありません。ミューテックス ロック操作は、通常、XCHG プロセッサ命令によって実装されます。これは、メモリ バリアとしても機能します (少なくとも Intel プロセッサでは)。変数の読み取りは、キャッシュからではなく、メモリから強制的に行われます。

于 2012-08-25T22:08:51.057 に答える
0

C++11 を使用している場合は、objatomic にすると、すべての問題getInstanceが解消されます。

static std::atomic<T*> obj;

template <typename T, typename Lock>
std::atomic<T*> Singleton<T, Lock>::obj = 0;

deleteInstanceもう少し作業が必要です。

T *tmp = 0;
obj.exchange(tmp);
delete tmp;

deleteInstanceクラスは未処理のポインタがあるかどうかわからないため、この関数を呼び出すと、無実のスレッドからオブジェクトがヤンクされる可能性があるため、危険です。

于 2012-08-26T00:30:18.777 に答える