#include<iostream>
using namespace std;
class singleton
{
private:
static singleton* ref;
singleton()
{
cout<<"singleton ctor"<<endl;
}
public:
static singleton* getInstance()
{
return ref;
}
};
singleton* singleton::ref=new singleton();
int main()
{
singleton* ref=singleton::getInstance()
}
答案 0 :(得分:3)
您的示例中没有线程。假设在静态初始化期间初始化的对象可能是产生线程并访问singleton::ref
,则代码很容易访问未初始化的内存,并且它不是线程安全的。如果在输入main()
后启动了第一个线程,则代码是线程安全的。
如果你想确保首次访问时对象是正确构造的,同时,即使在从多个线程进行静态初始化期间访问时,也要使构造线程安全,你可以使用
singleton* singleton::getInstance() {
static singleton rc;
return &rc;
}
标准中保证上述是线程安全的相关部分是6.7 [stmt.dcl]第4段:
...如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成...
第一次调用getInstance()
时将构造对象。即使多个线程同时调用getInstance()
,对象也只构造一次,并且对getInstance()
的并发调用将阻塞,直到构造完成。您可以确保getInstance()
的构造在静态初始化期间发生,方法是在singleton
的实现中使用它:
static singleton* constructionDummy = singleton::getInstance();
请注意,单线程通常会导致重大问题,在多线程程序中则更是如此。除了单线程已经在单线程程序中创建的问题之外,它们还进一步引入了数据竞争的潜力,并且为了解决数据竞争,倾向于引入序列化。除了设置严格不可变数据的单例外,我建议不使用它们。
答案 1 :(得分:0)
虽然作为一般原则,我同意这样的观点,即以明确定义的顺序创建对象是可取的,从线程安全的角度来看,问题中发布的实现比在getInstance中构造单例对象更安全。在后一种情况下,除非程序员明确地使用原子操作,理论上可以多次调用构造函数。以下是使用VS2013(无优化调试)对此代码的x86目标进行拆卸。
singleton* singleton::getInstance() {
static singleton rc;
return &rc;
}
static singleton* getInstance()
{
00E85820 push ebp
00E85821 mov ebp,esp
00E85823 push 0FFFFFFFFh
00E85825 push 0E89E6Eh
00E8582A mov eax,dword ptr fs:[00000000h]
00E85830 push eax
00E85831 sub esp,0C0h
00E85837 push ebx
00E85838 push esi
00E85839 push edi
00E8583A lea edi,[ebp-0CCh]
00E85840 mov ecx,30h
00E85845 mov eax,0CCCCCCCCh
00E8584A rep stos dword ptr es:[edi]
00E8584C mov eax,dword ptr ds:[00E8F000h]
00E85851 xor eax,ebp
00E85853 push eax
00E85854 lea eax,[ebp-0Ch]
00E85857 mov dword ptr fs:[00000000h],eax
static singleton ref;
00E8585D mov eax,dword ptr ds:[00E8F330h]
00E85862 and eax,1
00E85865 jne singleton::getInstance+6Ch (0E8588Ch)
00E85867 mov eax,dword ptr ds:[00E8F330h]
00E8586C or eax,1
00E8586F mov dword ptr ds:[00E8F330h],eax
00E85874 mov dword ptr [ebp-4],0
00E8587B mov ecx,0E8F32Ch
00E85880 call singleton::singleton (0E810D2h)
00E85885 mov dword ptr [ebp-4],0FFFFFFFFh
return &ref;
00E8588C mov eax,0E8F32Ch
}
dword ptr ds:[00E8F330h]用作标志来检查是否调用构造函数。
正如预期的那样,编译器不会发出原子比较和交换指令。因此,如果线程B在线程A执行指令00E8586F之前在00E8585D执行指令,则两者都将调用构造函数。但是,为了在实践中看到这一点,我们可能需要使用特殊的测试用例,其中多个线程被生成(在多核处理器上)并在事件/信号量上被阻塞。然后发信号通知信号量立刻释放所有线程,希望我们将看到构造函数将被多次调用。如果在getInstance中执行新的singleton(),则同样适用。
此问题并非特定于单身人士。
答案 2 :(得分:-1)
如果你的意思是线程安全,不同的线程会通过调用singleton::getInstance
得到正确的(相同的)指针,而不是:是的,它是线程安全的。
并且(正如评论中所指出的那样)你应该在main函数(或另一个函数)中以明确定义的顺序创建所有非平凡的静态变量!
所以写下来:
singleton* singleton::ref = NULL;
int main()
{
singleton::ref = new singleton();
// create objects which use the singleton
singleton* ref = singleton::getInstance();
// create threads...
}