【问题标题】:Differences between static declarations in singletons单例中静态声明的区别
【发布时间】:2015-04-19 10:58:00
【问题描述】:

(取自书中的代码:http://gameprogrammingpatterns.com/ by Robert Nystrom)

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

第一个:

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() {}
};

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

【问题讨论】:

    标签: c++ multithreading design-patterns singleton


    【解决方案1】:

    第一个版本不是线程安全的,多个线程可能会尝试在没有任何同步的情况下同时读取和修改instance_,这会导致竞争条件。

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

    如果多个线程尝试初始化相同的静态本地 变量并发,初始化只发生一次(类似 可以使用 std::call_once 获得任意函数的行为)

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

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

    【讨论】:

      【解决方案2】:

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

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

      在第二个代码 sn-p 设置 instance 指针是初始化。编译器保证静态变量的初始化将最多进行一次,而不管并发调用instance() 的线程数如何。本质上,系统中最多有一个单例的保证是由编译器提供的,没有任何显式的并发处理代码。

      【讨论】:

      • 这种情况只发生在 C++11 之后。
      【解决方案3】:

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

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

      这使得后来的设计线程对于 C++11 和更高版本的 C++ 编译器是安全的。尽管该设计在 C++03 和 C++98 实现中可能不安全。


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

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

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

      【讨论】:

      • 只从 C++11 开始保证局部静态变量的线程安全一次性初始化。而通过定义FileSystem 实例,您就无法实现lazy initialization 的目标。 std::scoped_ptr 在这里可能更合适。
      • @Lingxi 你是对的部分 C++98 没有引用多线程并且引用 C++98 3.6.3.1 [basic.start.term] 我会得出结论该构造未指定。关于惰性初始化,不用担心,编译器在初始化静态对象时已经很惰性了。自己试试吧。
      • 确实如此。我没有说清楚。甚至为FileSystem 预先分配的存储空间也根本不是问题。
      • 不@Lingxi,我的意思有点不同。我的观点是,编译器会懒得尽可能晚地推迟实例的创建。 (这几乎没有开销,但是编译是这样做的)。检查this example 以获得更好的理解。除非程序流遇到所需的声明,否则不会创建静态对象。 (我希望声明在技术上是正确的词)
      • 我同意你的观点,因为它会延迟初始化实例。但是,它不会像instance_ = new FileSystem(); 那样延迟分配存储空间。但同样,正如我所说,预先分配静态存储并不重要。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 1970-01-01
      • 1970-01-01
      • 2010-10-05
      • 1970-01-01
      • 2013-10-08
      相关资源
      最近更新 更多