• 本文内容衔接于前一篇文章(封装MutexLock、MutexLockGuard、Condition)

一、DCL

  • 研究Singleton的线程安全实现的历史会发现很多有意思的事情,人们一度认为double checked locking(缩写为DCL)是王道,兼顾了效率与正确性。后来有“神牛”指出由于乱序执行的影响,DCL是靠不住的。可以参阅:
  • Java开发者还算幸运,可以借助内部静态类的装载来实现。C++就比较惨,要么次次锁,要么eager initialize,或者动用memory barrier这样的“大杀器”
  • Java 5修订了内存模型,并给volatile赋予了acquire/release语义,这下DCL(with volatile)又是安全的了。然而C++的内存模型还在修订中(C++11已经有了全新定义的内存模式,可参阅:​​http://scottmeyers.blogspot.com/2012/04/information-on-c11-memory-model.html​​),C++的volatile目前还不能(将来也难说)保证DCL的正确性(只在Visual C++ 2005及以上版本有效)

二、pthread_once

  • 其实没有那么麻烦,在实践中用pthread_once就行:
template<typename T>
class Singleton :boost::noncopyable {
private:
static T& instance()
{
pthread_once(&ponce_, &Signleton::init);
return *value_;
}
private:
Singleton();
~Singleton();

static void init()
{
value_ = new T();
}
private:
static pthread_oncet ponce_;
static T* value_;
};

//必须在头文件中定义static变量
template<typename T>
pthread_oncet Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;

template<typename T>
T* Singleton<T>::value_ = NULL;
  • 上面这个Singleton没有任何花哨的技巧:
  • 它用pthread_once_t来保证 lazy-initialization的线程安全
  • 线程安全性由Pthreads库保证,如果系统的Pthreads库有bug,那就认命吧,多线程程序反正也不可能正确执行了
  • 使用方法也很简单:
Foo& foo = Singleton<Foo>::instance();
  • 这个Singleton没有考虑对象的销毁:
  • 在长时间运行的服务器程序里,这不是一个问题,反正进程也不打算正常退出(参阅后面的“分布式系统工程实践之能随时重启进行作为系统的重要目标”)
  • 在短期运行的程序中,程序退出的时候自然就释放所有资源了(前提是程序里不使用不能由操作系统自动关闭的资源,比如跨进程的mutex)
  • 在实际的muduo::Singleton class中,通过atexit提供了销毁功能,聊胜于无罢了
  • 另外,这个Singleton只能调用默认构造函数,如果用户想要指定T的构造方式,我们可以用模板特化技术来提供 一个定制点,这需要引入另一层间接

三、附加

  • 本专题未完结,参阅下一篇文章(借shared_ptr实现copy-on-write)