便携式线程安全懒惰单身人士

yut 发布于 2019-11-10 c++ 最后更新 2019-11-10 12:10 278 浏览

祝福大家。 我正在尝试编写一个线程安全的单身人士以供将来使用。这是我能想到的最好的。任何人都可以发现任何问题吗?关键假设是静态初始化发生在动态初始化之前的单个线程中。 (这将用于商业项目和公司不使用提振:(生活将是一件轻而易举的事情:)否则:) PS:还没有检查这个编译,我的道歉。

/*
There are two difficulties when implementing the singleton pattern:
Problem (a):  The "global variable instantiation fiasco". TODO: URL
This is due to the unspecified order in which global variables are initialised. Static class members are equivalent
to a global variable in C++ during initialisation.
Problem (b):  Multi-threading.
Care must be taken to ensure that the mutex initialisation is handled properly with respect to problem (a).
*/
/*
Things achieved, maybe:
*) Portable
*) Lazy creation.
*) Safe from unspecified order of global variable initialisation.
*) Thread-safe.
*) Mutex is properly initialise when invoked during global variable intialisation:
*) Effectively lock free in instance().
*/
/************************************************************************************
Platform dependent mutex implementation
*/
class Mutex
{
public:
 void lock();
 void unlock();
};
/************************************************************************************
Threadsafe singleton
*/
class Singleton
{
public:  // Interface
 static Singleton* Instance();
private:  // Static helper functions
static Mutex* getMutex();
private:  // Static members
static Singleton* _pInstance;
static Mutex* _pMutex;
private:  // Instance members
bool* _pInstanceCreated;  // This is here to convince myself that the compiler is not re-ordering instructions.
private:  // Singletons can't be coppied
explicit Singleton();
 ~Singleton() { }
};
/************************************************************************************
We can't use a static class member variable to initialised the mutex due to the unspecified
order of initialisation of global variables.
Calling this from
*/
Mutex* Singleton::getMutex()
{
 static Mutex* pMutex = 0;  // alternatively:  static Mutex* pMutex = new Mutex();
 if( !pMutex )
 { 
  pMutex = new Mutex();  // Constructor initialises the mutex: eg. pthread_mutex_init( ... )
 }
return pMutex;
}
/************************************************************************************
This static member variable ensures that we call Singleton::getMutex() at least once before
the main entry point of the program so that the mutex is always initialised before any threads
are created.
*/
Mutex* Singleton::_pMutex = Singleton::getMutex();
/************************************************************************************
Keep track of the singleton object for possible deletion.
*/
Singleton* Singleton::_pInstance = Singleton::Instance();
/************************************************************************************
Read the comments in Singleton::Instance().
*/
Singleton::Singleton( bool* pInstanceCreated )
{
 fprintf( stderr, "Constructor\n" );
_pInstanceCreated = pInstanceCreated; 
}
/************************************************************************************
Read the comments in Singleton::Instance().
*/
void Singleton::setInstanceCreated()
{
 _pInstanceCreated = true;
}
/************************************************************************************
Fingers crossed.
*/
Singleton* Singleton::Instance()
{
 /*
'instance' is initialised to zero the first time control flows over it. So
 avoids the unspecified order of global variable initialisation problem.
*/ 
 static Singleton* instance = 0;
/*
 When we do:
instance = new Singleton( instanceCreated );
the compiler can reorder instructions and any way it wants as long
 as the observed behaviour is consistent to that of a single threaded environment ( assuming
 that no thread-safe compiler flags are specified). The following is thus not threadsafe:
if( !instance )
 {
  lock();
  if( !instance )
  {
   instance = new Singleton( instanceCreated );
  }
  lock();
 }
Instead we use:
static bool instanceCreated = false;
as the initialisation indicator.
 */
 static bool instanceCreated = false;
/*
Double check pattern with a slight swist.
*/
 if( !instanceCreated )
 {
  getMutex()->lock();
  if( !instanceCreated )
  {
   /*
   The ctor keeps a persistent reference to 'instanceCreated'.
In order to convince our-selves of the correct order of initialisation (I think
   this is quite unecessary
   */
   instance = new Singleton( instanceCreated );
/*
   Set the reference to 'instanceCreated' to true.
Note that since setInstanceCreated() actually uses the non-static
   member variable: '_pInstanceCreated', I can't see the compiler taking the
   liberty to call Singleton's ctor AFTER the following call. (I don't know
   much about compiler optimisation, but I doubt that it will break up the ctor into
   two functions and call one part of it before the following call and the other part after.
   */
   instance->setInstanceCreated();
/*
   The double check pattern should now work.
   */
  }  
  getMutex()->unlock();
 }
return instance;
}
已邀请:

icum

赞同来自:

内容太长未翻译

psed

赞同来自:

在代码的全局范围内:

/************************************************************************************
Keep track of the singleton object for possible deletion.
*/
Singleton* Singleton::_pInstance = Singleton::Instance();
这使您的实现不是懒惰的。大概你想在全局范围内将_pInstance设置为NULL,并在解锁互斥锁之前在Instance()中构造单例后分配给它。

asaepe

赞同来自:

Meyers& amp; Alexandrescu,Singleton是特定目标:C++ and the Perils of Double-Checked Locking。这有点棘手的问题。

zcum

赞同来自:

如果您希望看到有关Singletons的深入讨论,关于其生命周期和线程安全问题的各种政策,我只能推荐一个很好的读物:Alexandrescu的“Modern C++ Design”。 实施在Loki的网站上展示,找到它here! 是的,它确实存在于单个头文件中。所以我真的鼓励你至少抓住文件并阅读它,更好的是阅读这本书以获得全面的反思。

tquod

赞同来自:

拥有一个在构造函数中什么也不做的非惰性单例通常会更好,然后在GetInstance中对一个分配任何昂贵资源的函数执行一次线程安全调用。你已经非懒惰地创建了一个Mutex,那么为什么不在你的Singleton对象中放入互斥量和某种Pimpl呢? 顺便说一句,这在Posix上更容易:

struct Singleton {
    static Singleton *GetInstance() {
        pthread_once(&control, doInit);
        return instance;
    }
private:
    static void doInit() {
        // slight problem: we can't throw from here, or fail
        try {
            instance = new Singleton();
        } catch (...) {
            // we could stash an error indicator in a static member,
            // and check it in GetInstance.
            std::abort();
        }
    }
static pthread_once_t control;
    static Singleton *instance;
};
pthread_once_t Singleton::control = PTHREAD_ONCE_INIT;
Singleton *Singleton::instance = 0;
确实存在用于Windows和其他平台的pthread_once实现。