(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() {}
};
后来他说第二个是更合适的方法,因为它是线程安全的,而第一个不是。
是什么让第二个线程安全?
这两者之间静态声明的区别是什么?
答案 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()
的线程数是多少,编译器都会保证静态变量的初始化最多只执行一次。从本质上讲,编译器提供系统中最多只有一个单例的保证,没有任何处理并发的显式代码。