C++实现线程安全的Singleton

来源:互联网 发布:新型网络犯罪案例分析 编辑:程序博客网 时间:2024/04/26 16:58

C++实现线程安全的Singleton

如何用C++实现线程安全的单例模式(singleton),本文汇总这方面的讨论,包括DCL(double-checked-locking)、meyers singleton和采用pthread_once()的方案,并最终决定在今后选择pthread_once()方案

在现在的我看来,陈硕的muduo是一个宝库,从中可以学到C++、网络编程、分布式应用、多线程服务程序等方面的实践知识。muduo不是朝夕之功,而且其背后隐藏的价值不下于代码本身,陈硕自2008年始就写作了多篇相关博文,很值得学习,比如“多线程服务器的常用编程模型”。此文提到了线程安全的Singleton实现,我想到去年曾了解这个话题,这次再遇,打算记录下来。

Double-Checked Locking Pattern

DCLP不是线程安全的,因为可能有乱序执行的影响,Meyers的这篇文章有讨论。

Sigleton* Singleton::instance() {    if (pInstance == 0) {        Lock lock;        if (pInstance == 0) {            pInstance = new Singleton;        }    }    return pInstance;}

Meyers Singleton

该实现使用了lazy initialization,看起来线程安全,但实际上不是的。

static Singleton& instance() {    static Singleton s;    return s;}

因为实际上编译器类似于如此处理(看这个链接,里面有不错的扩展资料):

static Singleton& instance() {    static bool initialized = false;    static char s[sizeof( Singleton)];    if (!initialized) {        initialized = true;        new( &s) Singleton(); // call placement new on s to construct it    }    return (*(reinterpret_cast<Singleton*>( &s)));}

pthread_once()

这个方案被stackoverflow上一些人以及陈硕支持,下面的代码来自于陈硕的博文。

#include <pthread.h>template<typename T>class Singleton : boost::noncopyable {public:    static T& instance() {        pthread_once(&ponce_, &Singleton::init);        return *value_;    }        static void init() {        value_ = new T();    }private:    static pthread_once_t ponce_;    static T* value_;};template<typename T>pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;template<typename T>T* Singleton<T>::value_ = NULL;
--EOF--



关于Singleton (单件),线程安全

作者:半瓶墨水   链接:http://www.2maomao.com/blog/when-cplusplus-meet-singleton/

很就没写技术相关的blog了,Twitter和Buzz都很少用了,甚至很少有时间静下来思考、敲打键盘

今天面试问到static关键字的时候,有个应试者提到了Singleton的实现,就顺便多问了一点儿,回来自己又查了查

Singleton,单件,四人帮(GoF)写design pattern那本书的时候提出来的,一个进程内有且只有一个实例

说起这些Design Pattern,当时一看,我操,酷毙了,你不知道都不好意思跟别人说你是搞编程的

后来,渐渐的发现,没有银弹,只是有些Design Pattern或许有些淫荡,比如这个Singleton

一个最初的C++标准实现是这样的(S-V1):

//——————Implementation #1 ———————-
//s.h
class Singleton {
  private:
    Singleton();
    static Singleton* instance_;
  public:
    static Singleton* getInstance() {
        if (!instance_)
            instance_ = new Singleton();
        return instance_; } };

//s.cpp
Singleton* Singleton::pInstance_ = 0;

注意其中构造函数是私有的,所有只能通过getInstance构造,构造前又会先检查,所以只有一个

后来有人说,这样不是线程安全的,哦,那就简单点儿,这样吧(S-V2):

//——————Implementation #2 ———————-
//s.h
class Singleton {
  private:
    Singleton();
  public:
    static Singleton* getInstance() {
        static Singleton* instance_ = new Singleton();
        return instance_; } };

结果有人说,这个是依赖于编译器的,GCC能保证static的初始化不会出问题,但是VC就难说了。
于是,有人觉得既然第一个实现稍微改改就行了,只要加一个淫荡的锁(S-V1.1):

//——————Implementation #1.1 ———————-
//s.h
class Singleton {
  private:
    Singleton();
    static Singleton* instance_;
  public:
    static Singleton* getInstance() {
        lock_mutex();
        if (!instance_)
            instance_ = new Singleton();
        unlock_mutex();
        return instance_; } };

//s.cpp
Singleton* Singleton::pInstance_ = 0;

再后来,有人说这样也不行啊,每次get都加锁,效率太低了吧,那就更加淫荡一点,加锁之前我先check,加锁以后,我再check(S-V1.2):

//——————Implementation #1.2 ———————-
//s.h
class Singleton {
  private:
    Singleton();
    static Singleton* instance_;
  public:
    static Singleton* getInstance() {
        if (!instance_) {
          lock_mutex();
          if (!instance_)
              instance_ = new Singleton();
          unlock_mutex();}
        return instance_; } };

//s.cpp
Singleton* Singleton::pInstance_ = 0;

到这里,在搜索网络以前,我曾经认为已经OK了,但是多CPU多线程的复杂超乎想象,有人说,后一个实现不是线程安全的,因为对于instance的读和写并不是原子操作会发生写一半读一半的情况!
好吧,对效率没啥要求每个singleton就只会用个几次的用S-V1.1,没有多线程的用S-V2


========== 以下我也不是很懂的分割线 ==========

难道真的没办法优化S-V1.1了吗?

有办法,那个instance读一半写一半的情况对于int这个类型是不会发生的,那么,就多来一步,check一个int类型的标识吧:

//——————Implementation #1.3 ———————-
//s.h
class Singleton {
  private:
    Singleton();
    static Singleton* instance_;
    static int flag_;
  public:
    static Singleton* getInstance() {
        if (!flag_) {
          lock_mutex();
          if (!instance_)
              instance_ = new Singleton();
          unlock_mutex();}
          flag_ = 1;
        return instance_; } };

嘿嘿,这下你该满足了吧,NO!,还是有问题的,具体啥问题,参见这里 和 那里,代码已经贴太多了。


进行到这里,你应该已经体会到,Singleton是很变态的,Design Pattern是很淫荡的。。。

Singleton最淫荡的地方在于:真正需要它的机会太少太少了,更多讨论参见:
[1] Singleton,你坏。。。在哪里?
[2] Singleton,银弹还是狗屎
[3] Singleton,何日才能用到你


路人甲:OMG,这是什么代码格式啊!
好吧,肯定有人不喜欢这个缩进,但是我敢肯定,肯定有人知道我喜欢Python了 :D






服务器公共库开发--线程安全的singleton类, 可配置的线程锁管理类

在服务器开发中,大量的使用了singleton模式, 以我的工作为例, 用于打印log的类是一个singleton, 内存池管理器是一个singleton...虽然singleton模式实现起来不难, 但是为了避免重复开发, 我还是决定应该把这个类的实现单独拿出来,同时, singleton类还需要支持多线程,但是我从来不写多线程的服务器程序, 对多线程的支持可以通过预编译宏来实现.我把操作多线程锁, 以及singleton类都放在这篇文章中, 多线程锁仅支持linux.

threadmutex.h
/********************************************************************
    created:    2008/08/01
    filename:     threadmutex.h
    author:        Lichuang
                
    purpose:    线程锁类, 由是否定义宏__USE_THREAD__来决定是否使用该类
********************************************************************
*/

#ifndef __THREAD_MUTEX_H__
#define __THREAD_MUTEX_H__

#ifdef __USE_THREAD__
    #include <pthread.h>
    
    #define THREAD_LOCK(tThreadMutex)     tThreadMutex.Lock()
    #define THREAD_UNLOCK(tThreadMutex)   tThreadMutex.UnLock()
#else
    #define THREAD_LOCK(tThreadMutex)     
    #define THREAD_UNLOCK(tThreadMutex)   
#endif

class CThreadMutex
{
public:
    CThreadMutex();
    ~CThreadMutex();
    int Lock();
    int UnLock();

private:
#ifdef __USE_THREAD__
    pthread_mutex_t  m_tThreadMutex;
#endif
};

#endif /* __THREAD_MUTEX_H__ */

threadmutex.cpp
/********************************************************************
    created:    2008/08/01
    filename:     threadmutex.h
    author:        Lichuang
                
    purpose:    线程锁类, 由是否定义宏__USE_THREAD__来决定是否使用该
                线程锁类
********************************************************************
*/

#include "threadmutex.h"

#ifdef __USE_THREAD__

CThreadMutex::CThreadMutex()
{        
    ::pthread_mutex_init(&m_tThreadMutex, NULL);
}

CThreadMutex::~CThreadMutex()
{  
    ::pthread_mutex_destroy(&m_tThreadMutex);
}

int CThreadMutex::Lock()
{
    return ::pthread_mutex_lock(&m_tThreadMutex);
}

int CThreadMutex::UnLock()
{
    return ::pthread_mutex_unlock(&m_tThreadMutex);
}

#else

CThreadMutex::CThreadMutex()
{        
}

CThreadMutex::~CThreadMutex()
{  
}

int CThreadMutex::Lock()
{
    return 0;
}

int CThreadMutex::UnLock()
{
    return 0;
}

#endif

singleton.h
/********************************************************************
    created:    2008/08/01
    filename:     singleton.h
    author:        Lichuang
                
    purpose:    实现单件模式的虚拟基类, 其它需要实现为singleton的类可以
                继承自这个类
                支持多线程, 采用智能指针实现自动回收内存
********************************************************************
*/

#ifndef __SINGLETON_H__
#define __SINGLETON_H__

#include <memory>
#include "threadmutex.h"

using namespace std;

#define DECLARE_SINGLETON_CLASS( type ) \
        friend class auto_ptr< type >;  \
        friend class CSingleton< type >;

template <class T>
class CSingleton
{
public:
    static T* GetInstance();

protected:
    CSingleton()
    {
    }
    virtual ~CSingleton()
    {
    }

protected:    
    friend class auto_ptr<CSingleton>;

    static auto_ptr<T> m_pInstance;
    static CThreadMutex m_tThreadMutex;
};

template <class T>
auto_ptr<T> CSingleton<T>::m_pInstance;

template <class T>
CThreadMutex CSingleton<T>::m_tThreadMutex;

template <class T>
inline T* CSingleton<T>::GetInstance()
{
    if (0 == m_pInstance.get())
    {
        THREAD_LOCK(m_tThreadMutex);
        if (0 == m_pInstance.get())
        {
            m_pInstance.reset(::new T);
        }
        THREAD_UNLOCK(m_tThreadMutex);
    }

    return m_pInstance.get();
}

#endif /* __SINGLETON_H__ */

使用示例:

#include <iostream>
#include "singleton.h"

using namespace std;

class CTestSingleton
    : public CSingleton<CTestSingleton>
{
public:

    void Set(int a)
    {
        m_a = a;
    }
    int Get()
    {
        return m_a;
    }

private:
    CTestSingleton()
        : m_a(0)
    {

    }
    DECLARE_SINGLETON_CLASS(CTestSingleton)
private:
    int m_a;
};

int main()
{
    if (NULL == CTestSingleton::GetInstance())
    {
        cout << "GetInstance() error!" << endl;
    }

    cout << "before set: " << CTestSingleton::GetInstance()->Get() << endl;

    CTestSingleton::GetInstance()->Set(100);

    cout << "after set: " << CTestSingleton::GetInstance()->Get() << endl;

    return 0;
}



volatile语义及线程安全singleton模式探讨

作者:Scott Meyers and Andrei Alexandrescu   译者: ChengHuige at gmail.com

1.引言 

详尽的讨论了volatile语义以及如何用C++实现线程安全的Singleton模式。 

主要参考Scott Meyers and Andrei Alexandrescu写的“C++ and the Perils of Double-Checked Locking”,这是2004年的文章,以及网上的其他资源。 

其他参考:

 

  • Threads Basics 
        http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/threadsintro.html
  •  The "Double-Checked Locking is Broken" Declaration

    http://www.cs.umd.edu/%7Epugh/java/memoryModel/DoubleCheckedLocking.html 

 

  • 非完美C++ Singleton实现[2]  Ismayday的官方技术博客
        http://hi.baidu.com/ismayday/blog/item/a797d6cae24b0d41f21fe788.html

 

 

  •  C++0x漫谈》系列之:多线程内存模型By 刘未鹏(pongba)
        http://blog.csdn.net/pongba/archive/2007/06/20/1659952.aspx

 

 

  • 一个老外的博客,包括需不需要对int加锁,gcc中的原子变量操作
        http://www.alexonlinux.com/ 

 

 

 

在前面我写了一个使用c++0x STL自带的多线程API的示例http://www.cnblogs.com/rocketfan/archive/2009/12/02/1615093.html

而这里涉及的不是多线程库的API,因为几乎所有的多线程互斥同步问题都可以通过互斥锁mutex和条件变量解决。但是频繁的加锁解锁很耗时,

毕竟用多线程就是要速度,本文涉及的都是相关的与线程库无关的问题。  

2.多线程问题简介

  一个 多线程的程序允许多个线程同时运行,可能要允许它们访问及更新它们共享的变量,每个线程都有其局部变量但是所有的线程都会看到同样的全局变量或者,“static"静态的类成员变量。

   不同的线程可能会运行在同一个处理器上,轮流执行(by interleaving execution).也有可能会运行在不同处理器上,所谓的hardware thread现在很常见。 

 3.volatile变量是什么

这个网上有很多介绍但都没有全面解释。而在“C++ and the Perils of Double-Checked Locking”的附注,作者给出了详细全面的解释。

作者是从volatile的产生讲起的,当时是为了统一的使用相同的地址处理内存地址和IO port地址,所谓memory-mapped I/O (MMIO)。

个人觉得本质都是不同层次存储映射关系吧,类比寄存器和内存。下面都用寄存器和内存解释。

  • 读的情况  

 

unsigned int *p = GetMagicAddress();

unsigned int a, b;

a = *p

b = *p; 

 考虑上面的代码,假设GetMagicAddress()是获得内存的地址,那么a = *p, b = *p,会被编译器认为是相同的操作,假设a = *p 使得值缓存在寄存器中,那么为了速度优化,编译器可能会将最后一行的代码换成

b = a;

 这样就不去内存读而是直接去寄存器读但这可能并不是我们想要的,因为如果这期间其他的线程改写了内存中*P的内容呢?

thread1             thread2 

a = *p;

                       *p = 3

b = a  //---------- 编译器优化的结果使得我们读到的并不是最新的p所指的内存的值

 

  • 写的情况

*p = a;

*p = b;

 

向上面的代码,编译器可能会认为*p = a是冗余操作从而去掉它,而这也可能不是我们想要的。

 

volatile的作用 :

volatile exists for specifying special treatment for such locations, specifically:

(1) the content of a volatile variable is “unstable” (can change by means unknown to the compiler),

(2) all writes to volatile data are “observable” so they must be executed religiously, and

(3) all operations on volatile data are executed in the sequence in which they appear in the source code.

  1. 被声明为volatile的变量其内容是不稳定的(unstable),它的值有可能由编译器所不能知晓的情况所改变
  2. 所有对声明为volatile的变量的写操作都是可见的,必须严格执行be executed religiously
  3. 所有对声明为volatile的变量的操作(读写)都必须严格按照源代码的顺序执行
所以上面的第一条确保读正确,第二条确保写正确,第三条确保读写混合的情况正确。JAVA更进一步跨越线程保证上面的条件。而C/C++只对单一线程内部保证。刘未鹏在博客中这么解释:“总而言之,由于C++03标准是单线程的,因此volatile只能保证单线程内语意。对于前面的那个例子,将xy设为volatile只能保证分别在Thread1Thread2中的两个操作是按代码顺序执行的,但并不能保证在Thread2“眼里”的Thread1的两个操作是按代码顺序执行的。也就是说,只能保证两个操作的线程内次序,不能保证它们的线程间次序。一句话,目前的volatile语意是无法保证多线程下的操作的正确性的。” 

 

但是即使是JAVA能够跨越线程保证,仍然是不够的因为volatile和非volatile操作之间的顺序仍然是未定义的,有可能产生问题,考虑下面的代码:

volatile int vi;

void bar(void) {
vi = 1;
foo();
vi = 0;
}

我们一般会认为vi会在调用foo之前设置为1,调用完后会被置为0。然而编译器不会对你保证这一点,它会很高兴的将你的foo()移位,比如跑到vi = 1前面,只要它知道在foo()里不会涉及到其它的volatile操作。所以安全的方法是用栅栏memory barrier例如“asm volatile (”" ::: “memory”)加到foo的前面和后面 来保证严格的执行顺序。

Meyers提到由于上面的原因我们通常会需要加大量的volatile变量,java1.5中的volatile给出了更严格简单的定义,所有对volatile的读操作,都将被确保发生在该语句后面的读写(any memory reference volatile or not)操作的前面。而写操作则保证会发生在该语句前面的读写操作的后面。.NET也定义了跨线程的volatile语意。

4.线程安全的C++ singleton模式

 

  • 最简单的singleton
注:下面有些来自原文,有些来自"非完美Singleton实现",其它参考之3。

 

复制代码
代码
 1  // from the header file
 2  class Singleton {
 3  public:
 4  static Singleton* instance();
 5  ...
 6  private:
 7  static Singleton* pInstance;
 8  };
 9 
10  // from the implementation file
11  Singleton* Singleton::pInstance = 0;
12 
13  Singleton* Singleton::instance() {
14  if (pInstance == 0) {
15     pInstance = new Singleton;
16  }
17  return pInstance;
18  }
复制代码

 

如果在单线程模式那么上面的代码除了instance()可能会有异常安全问题外没有太大问题。但是对于多线程而言,如果两个线程在14判断都读到pInstance = 0同时进入15,那么就会产生两个Singleton对象。而pInstance指向后产生的那一个。

 

  •  加锁保护的singleton
复制代码
代码
1 Singleton* Singleton::instance() {
2     Lock lock// acquire lock (params omitted for simplicity)
3     if (pInstance == 0) {
4         pInstance = new Singleton;
5     }
6     return pInstance;
7 // release lock (via Lock destructor)
复制代码

 

 

 

这样是绝对的安全了,但是由于加锁解锁的代价大,而instance又是可能被频繁调用的函数所以大大影响性能。事实上只要pInstance == 0的时候才可能出现问题,需要加锁,那么有了下面的写法:代码

复制代码
1 Singleton* Singleton::instance() {
2     if (pInstance == 0) {
3         Lock lock// acquire lock (params omitted for simplicity)
4         pInstance = new Singleton;
5     }
6     return pInstance;
7 // release lock (via Lock destructor)
复制代码

 

但是这样是错误的,因为两个线程如果同时在2判断为true,虽然会在3处互斥,但是还是会轮流进入保护区,生成两个Singleton.于是有人想到下面的方法。

 

  • DCL方法(Double Checked  Locking)
这也是ACE中Singleton的实现方法:

 

 代码

复制代码
1 Singleton* Singleton::instance() {
2     if (pInstance == 0) { // 1st test
3         Lock lock;
4         if (pInstance == 0) { // 2nd test
5             pInstance = new Singleton;
6         }
7     }
8     return pInstance;
9 }
复制代码

 

这看上去很完美但是它也是有问题的

在编译器未优化的情况下顺序如下:
1.new operator分配适当的内存;
2.在分配的内存上构造Singleton对象;
3.内存地址赋值给_instance。


但是当编译器优化后执行顺序可能如下:
1.new operator分配适当的内存;
2.内存地址赋值给_instance;

3.在分配的内存上构造Singleton对象。

 编译器优化后的代码看起来像下面这样:

 代码

复制代码
 1 Singleton* Singleton::instance() {
 2     if (pInstance == 0) {
 3         Lock lock;
 4         if (pInstance == 0) {
 5             pInstance = // Step 3
 6               operator new(sizeof(Singleton)); // Step 1
 7             new (pInstance) Singleton; // Step 2
 8          }
 9     }
10     return pInstance;
11 }
复制代码

 

 这样如果一个线程按照编译器优化的顺序执行到5,这时后pInstance就已经非0了,而实际上它所指向的内存上Singleton对象还没有被构造,这个时候有可能另一个线程运行到2,发现pInstance不是0,NULL,于是return pInstance,假设它又用这个指向还未构造对象的指针调用pInstance->doSomeThing() .........:(

 

 未了避免这种情况,于是有了下面的做法

 代码

复制代码
 1 Singleton* Singleton::instance() {
 2      if (pInstance == 0) {
 3          Lock lock;
 4          if (pInstance == 0) {
 5               Singleton* temp = new Singleton; // initialize to temp
 6               pInstance = temp; // assign temp to pInstance
 7          }
 8      }
 9      return pInstance;
10 }
复制代码

 

企图利用一个临时变量,仅当Singleton对象构造完成后才把地址赋给pInstance,然而很不幸,编译器会把你的临时变量视为无用的东西从而优化掉。。。。 

这里插一句为什么用pthread之类的库能够解决问题,保证顺序,因为它们是非语言本身的,往往 强迫编译器产生与之适应的代码,往往会调用系统调用,很多是用汇编实现的。

 

  • 加入volatile
前面讲到volatile的定义,那么这里能否利用volatile呢。上面最后的代码想法不错,只是编译器会捣乱:)那么我们加入volatile来避免编译器的优化,保证语句的顺序执行。不过这里我们考虑这种情况,Singlton带有一个变量x,默认的私有构造函数会将其赋值为5.

 

复制代码
代码
 1 class Singleton {
 2  public:
 3      static Singleton* instance();
 4      ...
 5  private:
 6      static Singleton* volatile pInstance; // volatile added
 7      int x;
 8      Singleton() : x(5) {}
 9  };
10  
11  // from the implementation file
12  Singleton* Singleton::pInstance = 0;
13  
14  Singleton* Singleton::instance() {
15      if (pInstance == 0) {
16          Lock lock;
17          if (pInstance == 0) {
18              Singleton* volatile temp = new Singleton; // volatile added
19              pInstance = temp;
20          }
21      }
22      return pInstance;
23 }
复制代码

 

 

下面考虑编译器inline构造函数和之后的instance()函数的样子

 代码

复制代码
1 if (pInstance == 0) {
2     Lock lock;
3     if (pInstance == 0) {
4         Singleton* volatile temp =
5         static_cast<Singleton*>(operator new(sizeof(Singleton)));
6         temp->= 5// inlined Singleton constructor
7         pInstance = temp;
8     }
9 }
复制代码

 

问题出来了,尽管temp被声明为volatile,但是*temp不是,这意味着temp->x也不是,这意味着编译器可能会把6,7句换位置,从而使得另一个线程得到一个指向x域并未被构造好的Sigleton对象!

解决办法是我们不仅仅声明pInstance为volatile也将*pInstance声明为volatile.如下:

复制代码
代码
 1 class Singleton {
 2 public:
 3     static volatile Singleton* volatile instance();
 4     ...
 5 private:
 6     // one more volatile added
 7     static Singleton* volatile pInstance;
 8 };
 9 
10 // from the implementation file
11 volatile Singleton* volatile Singleton::pInstance = 0;
12 volatile Singleton* volatile Singleton::instance() {
13     if (pInstance == 0) {
14         Lock lock;
15         if (pInstance == 0) {
16             // one more volatile added
17             volatile Singleton* volatile temp = new volatile Singleton;
18             pInstance = temp;
19         }
20     return pInstance;
21 }
复制代码

 

这里Lock不需要声明为volatile因为它来自其它的线程库入pthread会提供相应保证其前面的代码不会被编译器调到后面,它后面的代码也不会被调到前面,相当于栅栏效应。

     也许到现在这个版本是很完美的了,但是仍然可能失败,有下面两个原因:

 

  1. the Standard’s constraints on observable behavior are only for an abstract machine defined by the Standard, and that abstract machine has no notion of multiple threads of execution. As a result, though the Standard prevents compilers from reordering reads and writes to volatile data within a thread, it imposes no constraints at all on such reorderings across threads. 也就是说还是上面volatile解释语义时提到的,volatile,只保证线程内部顺序,不保证线程之间的
  2. 就像const变量直到其构造函数完成之后不是const的一样,volatile变量直到其构造函数和完成之前也不是volatile的volatile Singleton* volatile temp = new volatile Singleton; 要生成的变量直到new volatile Singleton完成之后才是volatile的。这意味这temp->x在构造函数中不是volatile的从而还是可能产生上面的问题和pInstance = temp 被编译器调换顺序。这个问题是可以解决的,办法如下,对默认构造函数改写,将对x的初始化列表构造初始化改为在构造函数中显示赋值。
1 Singleton()
2 {
3     static_cast<volatile int&>(x) = 5// note cast to volatile
4 }    

 

这样的work around就解决了问题。

编译器会生成类似下面的代码:

 代码

复制代码
 1 Singleton* Singleton::instance()
 2 {
 3     if (pInstance == 0) {
 4         Lock lock;
 5         if (pInstance == 0) {
 6            Singleton* volatile temp =
 7              static_cast<Singleton*>(operator new(sizeof(Singleton)));
 8            static_cast<volatile int&>(temp->x) = 5;
 9            pInstance = temp;
10         }
11     }
12 }
复制代码

 

 

这保证了8和9不会被调换顺序。

 

 

  • 一个可行的解决方案

 

 如果你的程序运行在多处理器机器上,会面临CACHE一致性问题,就是说每一个处理器会有自己的CACHE,而所有的处理器有共享的MAIN CACHE,什么时候将自己的CAHE内容写到MAIN CACHE中,以及什么时候去从MAIN CACHE中读数据都是问题,编译器有时候可能会把多个写入MAIN CACHE的数据按照地址升序写入已达到最佳速度,从而调换代码执行顺序。一般而言解决CACHE一致性问题采用栅栏,barrier方法。

 

复制代码
代码
 1 Singleton* Singleton::instance () {
 2     Singleton* tmp = pInstance;
 3     
 4     ... // insert memory barrier
 5     
 6     if (tmp == 0) {
 7         Lock lock;
 8         tmp = pInstance;
 9         if (tmp == 0) {
10             tmp = new Singleton;
11     
12     ... // insert memory barrier
13             pInstance = tmp;
14         }
15     }
16     return tmp;
17 }
复制代码

 

 

事实上我们不需要完整的双向栅栏,上面一处栅栏只需要保证前面的代码不要提到栅栏后面去,下面的栅栏保证后面的代码不要挪到栅栏前面去。 

但是注意栅栏是平台相关的,尤其是assembler. 如果你的平台支持栅栏那么这就是一个可行的解决方案。注意GCC4.4.2中的string对于ref count引用计数,就是采用原子操作加栅栏实现的。

 

  •  线程安全的Singleton模式总结
1.首先这里上面主要是讨论线程安全的问题,其实可以有很多可改变的地方。比如说第一种加锁的方案它是简洁安全的但是效率低,我们其实可以建议客户端代码这样写来减少对instance函数的调用,只需要先保存一个Singleton指针即可。

 

Singleton *instance = Singleton::instance();

instance->doThing1();

instance->donThing2();

而不要每次都调用instance(), Singleton::instance()->doThings().

 也可以先做实验看看直接用这种加锁的方案是否真的很大程度影响了效率值得你去改变它。 

 

2. 另外我们上面演示的代码都是采用了lazily-initialized Singleton,就是说只有用到的时候才会有Singleton对象产生,如果程序没有调用instance就不会有Singleton对象的产生。这样固然比eager initialization好,但是真的那么有必要吗,如果我们采用 eager initialization在进入main之前产生Singleton对象

而一般的多线程程序都是在进入main后再启动的其它线程,就是说进入 mian之前是单线程环境,于是就没有这么多问题了。。。。boost 就是这么干的。

"1) sington 在进入main函数前初始化.

2)第一次使用时, singlton已得到正确的初始化(包括在static code中情况). Written by gavinkwoe"

"由于create_object将在被调用(static object_type & instance())之前进行初始化,因此singleton_default对象的初始化被放到了main之前。非常巧妙的一个设计" 

 可以参考 游戏人生博客 Writen by Fox(yulefox.at.gmail.com) 设计模式(三)——Singleton

http://www.cppblog.com/Fox/archive/2009/09/22/96898.html  http://www.yulefox.com/20081119/design-patterns-03.html/

复制代码
代码
 1 template <typename T>
 2 struct singleton
 3 {
 4   private:
 5         struct object_creator
 6        {
 7                object_creator() { singleton<T>::instance(); }  // 创建实例
 8                inline void do_nothing() const { } 
 9        };
10      static object_creator create_object;
11      singleton();
12   public:
13      typedef  T  object_type;
14      static object_type & instance()
15     {
16          static object_type obj;
17          create_object.do_nothing();// 需要create_object的初始化
18          return obj;
19     }
20 };
21 template <typename T>  typename singleton<T>::object_creator singleton<T>::create_object; 
22  
复制代码

 

 这个设计用到了模板可复用,如Singleton<MySingletonClass>::instance().

 

 3. 如果我们允许每个线程能有一个自己的Singleton对象呢,那样就既可以推迟Singleton对象生成,也不用考虑多线程问题。

 

The end ~


原创粉丝点击