单例中静态声明之间的差异

时间:2015-04-19 10:58:01

标签: c++ multithreading design-patterns singleton

(Robert Nystrom撰写的代码:http://gameprogrammingpatterns.com/

在上面的书中,作者提供了两种制作单例类的方法:

第一个:

class FileSystem
{
public:
  static FileSystem& instance()
  {
    // Lazy initialize.
    if (instance_ == NULL) instance_ = new FileSystem();
    return *instance_;
  }

private:
  FileSystem() {}

  static FileSystem* instance_;
};

第二个:

class FileSystem
{
public:
  static FileSystem& instance()
  {
    static FileSystem *instance = new FileSystem();
    return *instance;
  }

private:
  FileSystem() {}
};

后来他说第二个是更合适的方法,因为它是线程安全的,而第一个不是。
是什么让第二个线程安全?
这两者之间静态声明的区别是什么?

3 个答案:

答案 0 :(得分:3)

对于多个线程,第一个版本不是线程安全的,可能会尝试同时读取和修改instance_而不进行任何同步,这会导致竞争条件。

第二个版本是自C ++ 11以来的线程安全的。引自cppreference静态局部变量部分):

  

如果多个线程尝试初始化相同的静态本地   并发变量,初始化恰好发生一次(类似   使用std :: call_once)

可以获得任意函数的行为

有了这个保证,对instance的修改只发生一次,并发读取没有问题。

但是,第二个版本在C ++ 11之前不是线程安全的。

答案 1 :(得分:1)

在前一种情况下,如果两个线程同时尝试创建实例,则可能会创建2个(或更多)单个对象的副本。 (如果两者都观察到**instance_**为NULL,则两者都创建new实例)。 (更糟糕的是,创建第一个实例的线程可能在后续调用中获得不同的实例值)

第二个使用static初始化,并且在第一次调用函数时构造对象。因此编译器保证static FileSystem *instance = new FileSystem();在程序单一的生命周期内最多只能执行一次,因此任何时候都会存在atmist单个副本。

这使得后来的设计线程对于C ++ 11和以后的complian C ++编译器是安全的。虽然在C ++ 03和C ++ 98实现中设计可能不安全。


以前设计的另一个缺点是,对象无法被破坏,而在以后的设计中,可以通过将instance_的类型更改为static FileSystem来破坏对象。即。

static FileSystem& instance()
{
  static FileSystem instance;
  return instance;
}

相关:Is Meyers implementation of Singleton pattern thread safe?

答案 2 :(得分:1)

在第一个代码段设置中,指向单例的instance_指针是赋值。它没有得到编译器的任何特殊处理。特别是,如果从并发线程调用instance(),则可以多次执行。

当两个并发线程尝试评估instance_ == NULL并获得true时,这会产生问题。此时,两个线程都会创建一个新实例,并将其分配给instance_变量。分配给instance_的第一个指针被泄露,因为第二个线程会立即覆盖它,导致对象无法访问。

在第二个代码段设置中,instance指针是初始化。无论同时调用instance()的线程数是多少,编译器都会保证静态变量的初始化最多只执行一次。从本质上讲,编译器提供系统中最多只有一个单例的保证,没有任何处理并发的显式代码。