C++多线程的Singleton(1)

C++多线程的Singleton(2)

 

Singleton是我使用过的最多的设计模式,也是日常工作中大家会经常用到的设计模式。其实,在C++里面写一个Singleton,不是一件非常容易的事情,以至于《C++设计新思维》里面花了一章内容专门讲解。难点在哪里呢?其实就是两个:

(1)多线程的并发性 (如果你的程序是单线程的,那么就没有这个问题)

(2)生命周期

 

下面我们就开始这个旅程。另外一点,我现在使用C++的原则是KISS,保持设计和代码的简单性。

 

首先,我们先来看看这两个问题。

(1)多线程的并发性,这不难理解。如果在单线程模式下,我们通常会这么写Singleton:

// Singleton.h
class Singleton
{
  public:
     ~Singleton(){}
     static Singleton& getInstance();

  private:
     Singleton(){}
     
};

// Singleton.cpp

/* static */
Singleton& Singleton::getInstance()
{
  static Singleton *instance = new Singleton;
  return &instance;
} 

  

 这种方法是按照《Effective C++》里面的建议,不使用class static变量,而使用函数static变量。这个是延迟初始化,如果整个过程没有函数调用Singleton::getInstance(),那么就不会有Singleton这个对象生成。为什么返回一个Reference,而不是一个指针,是因为这样不容易被误delete。可是如果是多线程呢?有Java经验的同学都会想到下面这个方法:

 

// Singleton.h 错误的实例
class Singleton
{
  public:
     ~Singleton(){}
     static Singleton& getInstance();

  private:
     Singleton(){}
     static Lock lock_;
};

// Singleton.cpp

/* static */
Lock Singleton::lock_;

Singleton& Singleton::getInstance()
{
  static Singleton *instance = NULL;
  if (NULL == instance) // thread B Pos1
  {
    LockHandler handler(lock_);  
    if (NULL == instance)
    {
      instance = new Singleton;  // thread A   Pos2
    }
  }
  return &instance;
} 

 (注解:Lock是类似与封装了pthread_mutex_t的类,具有lock(), unlock(), trylock()这样的方法;而LockHandler的构造函数就会调用lock_.lock(),析构函数会调用lock_.unlock(),这是一种RIIA的惯用法)。这段代码非常Java的Singleton代码,它有一个响亮的名字:两次检查。

 

public class Singleton {
    private Singleton(){}
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
          synchronized(Singleton.class) {
            if (instance == null) {
              instance = new Singleton();  
            }
          }
        }
    }
}

上面这段Java代码在Java5之后是正确的,在Java5之前,在有些平台上这段代码是有bug的。那C++的两次检查呢?是错误的。知道为什么这段代码是错误的,只会让让你很郁闷,因为instance = new Singleton这句话,根据正常的理解,应该是Singleton分好内存,然后再调用构造函数,然后再把那个地址赋值给instance。可是事实上这是不一定的,有可能先完成这次赋值,再调用构造函数。也许你应该明白为什么有问题了吧。假设threadA到了pos2那个地方,此时threadB刚好来到pos1,然后它发现instance==null不成立,于是threadB就可以使用instance了。可是此时,instance指向的对象还没有正常的调用构造函数。换句话说,threadB使用了一个没有被正确初始化的对象。看到这里也许你要哭了,没错,我也哭了。这不是我们程序员本需要考虑的问题,可是它就是像座山一样在那里。没办法,如果你使用C++,那么请接受它吧。我可以说这种问题很难测试到,也许它会跟随你的系统几个月甚至上年,可是有一天它突然崩溃了,你才知道原来是这样造成的。没办法,那我们退回到一个保守的地方:

 

// Singleton.h
class Singleton
{
  public:
     ~Singleton(){}
     static Singleton& getInstance();

  private:
     Singleton(){}
     static Lock lock_;
};

// Singleton.cpp

/* static */
Lock Singleton::lock_;

Singleton& Singleton::getInstance()
{
  static Singleton *instance = NULL;
  LockHandler handler(lock_); 
  if (NULL == instance) // thread B Pos1
  {
    instance = new Singleton;  // thread A   Pos2
  }
  return &instance;
} 

 这个代码是对的,正确的。可是它有些效率问题,因为你每次访问这个资源的时候,都需要去竞争这个lock_。我们需要继续优化这个。(未完待续)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值