当必须使用C ++ 11实现线程安全单例时,我知道的唯一正确的实现如下:
// header
class Singleton final {
public:
static Singleton& getInstance();
private:
Singleton() = default;
Singleton(Singleton const&) = delete;
void operator=(Singleton const&) = delete;
};
// implementation:
Singleton& Singleton::getInstance() {
static Singleton instance;
return instance;
}
在他的书“C ++ Concurrency in Action”中A. Williams写道,自从C ++ 11“初始化被定义为恰好在一个线程上发生”,所以这个“可以用作std :: call_once的替代”何时需要单个全局实例。当在如上定义的情况下调用Singleton的析构函数时,我会发出声音。
标准(ISO / IEC 14882:2011)定义为§3.6.3e的一部分。克。
初始化对象的析构函数(即其对象的对象) 生命周期已开始)静态存储持续时间被称为a 从main返回的结果以及调用std :: exit的结果。
和
调用cstdlib中声明的函数std :: abort()会终止 程序没有执行任何析构函数,也没有调用 函数传递给std :: atexit()或std :: at_quick_exit()。
那么首先在干净的出口(从主回归)发生什么?是否在“具有静态存储持续时间的初始化对象被调用”的析构函数之前或之后停止所有线程?
我知道使用共享库提供的单例是一个坏主意(可能在其他可能使用它的部分之前卸载)。 当调用Singleton :: getInstance()时会发生什么。 G。从其他(独立)线程?这可能会导致未定义的行为或所有线程 (分离与否)在调用静态变量的析构函数之前终止/连接?
(要明确的是:我认为单身是一种反模式,但是当我必须使用它时,我想知道会发生什么样的不良事情。)
答案 0 :(得分:2)
那么干净的
exit
(从main
返回)首先会发生什么?是否在析构函数之前或之后停止所有线程"对于具有静态存储持续时间的初始化对象被调用"?
std::exit
无需停止任何主题,exit
或_Exit
也不例外。部分是因为突然终止另一个线程可能会在错误的时刻终止它并导致其他线程死锁。
当C ++或C运行时终止并且通过调用exit_group(在Linux上)将控制流传递回操作系统时,线程终止:
这个系统调用等同于
_exit(2)
,除了它不仅终止调用线程,而且终止调用进程的线程组中的所有线程。此系统调用不会返回。
这意味着全局对象的析构函数与进程中的其他现有线程并行运行。在调用std::exit
或从main
返回之前,您必须以合作的方式明确终止所有其他线程。
答案 1 :(得分:0)
当最后一个线程退出用户代码时,以相反的顺序调用全局静态和单例函数静态析构函数,因此不存在多线程问题。主线程可以退出并使其他线程运行。只有当所有这些线程死亡时,程序才会真正关闭。
单身人士很高兴避免在构造订单静态时没有任何保证的问题,并且如果一个静态在构造期间依赖于另一个静态的内容,那么行为是不确定的。对于单身人士,您可以根据需要有效地创建静态。
你必须要注意的一个问题是关机期间的凤凰单身人士。单例静态以相反的顺序被破坏以完成它们的构造,但是另一个先前构造的静态/单例(A)对象,并且在构造期间没有调用单例(B)可能在单身(B)被破坏之后,em>在其自身(A)破坏期间调用单身人士(B)。
没有实际的机制可以解决这个问题,因此std :: string使用引用计数而不依赖于singleton析构函数。
根据特定的单例,您可以将静态数据保留在可识别的凤凰状态,因此它可以自我复活,也可以自行禁用。
例如,可以包装一个全局互斥锁,以便它在被混淆后实际上不会锁定。只有一个线程,所以谁在乎呢?调试记录器可以暂时以附加模式重新打开日志文件,如果是phoenixed,可能会发出一条消息,说它被调用太晚了。请注意,这些后期创建的对象永远不会被自动销毁。