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

时间:2009-12-09 23:40:02

标签: c++ multithreading singleton

向所有人致以问候。

我正在尝试编写一个线程安全的懒惰单例以备将来使用。这是我能想到的最好的。有人能发现任何问题吗?关键假设是静态初始化在动态初始化之前发生在单个线程中。 (这将用于商业项目,公司不使用提升:(,生活将是轻而易举的事情:)

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;
}

5 个答案:

答案 0 :(得分:8)

答案 1 :(得分:4)

通常最好有一个非延迟单例,它在构造函数中什么都不做,然后在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实现。

答案 2 :(得分:2)

如果您希望深入讨论单身人士,关于他们的生命周期和线程安全问题的各种政策,我只能推荐一个好的读物:“Modern C++ Design”由Alexandrescu撰写。

该实施在Loki的网站上展示,找到它here

是的,它确实存在于单个头文件中。所以我真的鼓励你至少抓住文件并阅读它,最好还是阅读这本书以获得全面的反思。

答案 3 :(得分:1)

代码中的全局范围:

/************************************************************************************
Keep track of the singleton object for possible deletion.

*/
Singleton* Singleton::_pInstance = Singleton::Instance();

这使您的实现不是懒惰的。大概你想在全局范围内将_pInstance设置为NULL,并在解锁互斥锁之前在Instance()中构造单例之后为其分配。

答案 4 :(得分:0)

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