单例中静态声明的区别
Differences between static declarations in singletons
(代码取自书籍:http://gameprogrammingpatterns.com/ by Robert Nystrom)
作者在上面的书中给出了两种不同的单例制作方法class:
第一个:
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() {}
};
后来他说第二种方法更合适,因为它是线程安全的,而第一种不是。
是什么让第二个线程安全?
这两者在静态声明上有什么区别?
在前一种情况下,如果两个线程尝试同时创建实例,则可能会创建 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?
在第一个代码片段中,将 instance_
指针设置为单例是一个 赋值 。它没有得到编译器的任何特殊处理。特别是,如果从并发线程调用 instance()
,则可以执行多次。
当两个并发线程尝试评估 instance_ == NULL
并获得 true
时,这会产生问题。此时两个线程都创建了一个新实例,并将其分配给 instance_
变量。分配给 instance_
的第一个指针已泄漏,因为第二个线程立即覆盖它,导致对象无法访问。
在第二个代码片段中设置 instance
指针是 初始化 。编译器保证静态变量的初始化最多完成一次,而不管同时调用 instance()
的线程数是多少。本质上,系统中最多有一个单例的保证是由编译器提供的,没有任何用于处理并发的显式代码。
第一个版本不是线程安全的,因为多个线程可能会尝试在没有任何同步的情况下同时读取和修改 instance_
,这会导致竞争条件。
从 C++11 开始,第二个版本是线程安全的。引自 cppreference(静态局部变量 部分):
If multiple threads attempt to initialize the same static local
variable concurrently, the initialization occurs exactly once (similar
behavior can be obtained for arbitrary functions with std::call_once)
有了这个保证,对instance
的修改只发生一次,并发读取没有问题。
不过,第二个版本在 C++11 之前不是线程安全的。
(代码取自书籍:http://gameprogrammingpatterns.com/ by Robert Nystrom)
作者在上面的书中给出了两种不同的单例制作方法class:
第一个:
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() {}
};
后来他说第二种方法更合适,因为它是线程安全的,而第一种不是。
是什么让第二个线程安全?
这两者在静态声明上有什么区别?
在前一种情况下,如果两个线程尝试同时创建实例,则可能会创建 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?
在第一个代码片段中,将 instance_
指针设置为单例是一个 赋值 。它没有得到编译器的任何特殊处理。特别是,如果从并发线程调用 instance()
,则可以执行多次。
当两个并发线程尝试评估 instance_ == NULL
并获得 true
时,这会产生问题。此时两个线程都创建了一个新实例,并将其分配给 instance_
变量。分配给 instance_
的第一个指针已泄漏,因为第二个线程立即覆盖它,导致对象无法访问。
在第二个代码片段中设置 instance
指针是 初始化 。编译器保证静态变量的初始化最多完成一次,而不管同时调用 instance()
的线程数是多少。本质上,系统中最多有一个单例的保证是由编译器提供的,没有任何用于处理并发的显式代码。
第一个版本不是线程安全的,因为多个线程可能会尝试在没有任何同步的情况下同时读取和修改 instance_
,这会导致竞争条件。
从 C++11 开始,第二个版本是线程安全的。引自 cppreference(静态局部变量 部分):
If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once)
有了这个保证,对instance
的修改只发生一次,并发读取没有问题。
不过,第二个版本在 C++11 之前不是线程安全的。