この質問はインタビューで尋ねられました。最初の部分はシングルトンクラスを書くことでした:
_class Singleton
{
static Singleton *singletonInstance;
Singleton() {}
public:
static Singleton* getSingletonInstance()
{
if(singletonInstance == null)
{
singletonInstance = new Singleton();
}
return singletonInstance;
}
};
_
次に、マルチスレッドの状況でこのgetSingletonInstance()
を処理する方法を尋ねられました。私は本当にわかりませんでしたが、次のように変更しました:
_class Singleton
{
static Singleton *singletonInstance;
Singleton() {}
static mutex m_;
public:
static Singleton* getSingletonInstance()
{
m_pend();
if(singletonInstance == null)
{
singletonInstance = new Singleton();
}
return singletonInstance;
}
static void releaseSingleton()
{
m_post();
}
};
_
次に、ミューテックスは必須ですが、ミューテックスの保留とポストは時間がかかるため効率的ではないと言われました。そして、この状況に対処するためのより良い方法があります。
マルチスレッドの状況でシングルトンクラスを処理するためのより良い、より効率的な方法を誰かが知っていますか?
C++ 11では、以下がスレッドセーフな初期化を実行することが保証されています。
static Singleton* getSingletonInstance()
{
static Singleton instance;
return &instance;
}
C++ 03では、一般的なアプローチは、ダブルチェックロックを使用することでした。フラグ(またはポインタ自体)をチェックして、オブジェクトが初期化されていない可能性があるかどうかを確認し、可能であればミューテックスのみをロックします。これには、ポインタ(または関連するブールフラグ)をアトミックに読み取る何らかの非標準的な方法が必要です。多くの実装では、プレーンポインタまたはbool
を誤って使用していますが、1つのプロセッサでの変更が他のプロセッサで認識される保証はありません。コードは次のようになるかもしれませんが、私は間違いなく何か間違ったことを持っています:
static Singleton* getSingletonInstance()
{
if (!atomic_read(singletonInstance)) {
mutex_lock lock(mutex);
if (!atomic_read(singletonInstance)) {
atomic_write(singletonInstance, new Singleton);
}
}
return singletonInstance;
}
これを正しく行うのは非常に難しいので、気にしないことをお勧めします。 C++ 11では、何らかの理由でサンプルの動的割り当てを維持したい場合は、標準のアトミックタイプとミューテックスタイプを使用できます。
ここでは、同期化された初期化についてのみ話していることに注意してください。オブジェクトへの同期化されたアクセスではありません(アクセサのmutexをロックして、後で別の関数を介して解放することによって、バージョンが提供します)。オブジェクト自体に安全にアクセスするためにロックが必要な場合は、すべてのアクセスでロックを回避することはできません。
@piokucが示唆したように、ここでonce関数を使用することもできます。 C++ 11の場合:
#include <mutex>
static void init_singleton() {
singletonInstance = new Singleton;
}
static std::once_flag singleton_flag;
Singleton* getSingletonInstance() {
std::call_once(singleton_flag, init_singleton);
return singletonInstance;
}
そして、はい、これはnew Singleton
は例外をスローします。
インスタンスではなく、実際にはシングルトンをロックする必要があります。インスタンスがロックを必要とする場合、それは呼び出し元によって(または、インスタンスが公開するインターフェースの種類に応じて、おそらくインスタンス自体によって)処理される必要があります。
更新サンプルコード:
#include <mutex>
class Singleton
{
static Singleton *singletonInstance;
Singleton() {}
static std::mutex m_;
public:
static Singleton* getSingletonInstance()
{
std::lock_guard<std::mutex> lock(m_);
if(singletonInstance == nullptr)
{
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
C++ 11を使用している場合は、singletonInstance
をアトミック変数にしてから、ダブルチェックロックを使用できます。
if (singletonInstance == NULL) {
lock the mutex
if (singletonInstance == NULL) {
singletonInstance = new Singleton;
}
unlock the mutex
}
return singletonInstance;
POSIXスレッドを使用する場合は、pthread_once_t
およびpthread_key_t
などを使用できるため、ミューテックスの使用を完全に回避できます。例えば:
template<class T> class ThreadSingleton : private NonCopyable {
public:
ThreadSingleton();
~ThreadSingleton();
static T& instance();
private:
ThreadSingleton( const ThreadSingleton& );
const ThreadSingleton& operator=( const ThreadSingleton& )
static pthread_once_t once_;
static pthread_key_t key_;
static void init(void);
static void cleanUp(void*);
};
そして実装:
template<class T> pthread_once_t ThreadSingleton<T>::once_ = PTHREAD_ONCE_INIT;
template<class T> pthread_key_t ThreadSingleton<T>::key_;
template<class T>
T& ThreadSingleton<T>::instance()
{
pthread_once(&once_,init);
T* value = (T*)pthread_getspecific(key_);
if(!value)
{
value = new T();
pthread_setspecific(key_,value);
}
return *value;
}
template<class T> void ThreadSingleton<T>::cleanUp(void* data)
{
delete (T*)data;
pthread_setspecific(key_,0);
}
template<class T> void ThreadSingleton<T>::init()
{
pthread_key_create(&key_,cleanUp);
}