pau*_*doo 36 c++ singleton construction multithreading lazy-initialization
有没有办法在C++中实现单例对象:
(我不太了解我的C++,但是在执行任何代码之前初始化积分和常量静态变量的情况(即,甚至在执行静态构造函数之前 - 它们的值可能已在程序中"初始化")如果是这样的话 - 也许这可以被利用来实现单例互斥体 - 这反过来可以用来保护真正的单例的创建......)
很好,现在我似乎有几个好的答案(羞耻我不能标记2或3作为答案).似乎有两个广泛的解决方案:
Chr*_*son 14
不幸的是,Matt的答案以C/C++内存模型不支持的双重检查锁定为特色.(它受Java 1.5及更高版本支持 - 我认为是.NET内存模型.)这意味着在pObj == NULL检查发生的时间和获取锁定(互斥锁)之间,pObj可能已经在另一个线程上分配了.线程切换在操作系统需要时发生,而不是在程序的"行"之间发生(在大多数语言中没有意义的后编译).
此外,正如Matt承认的那样,他使用的int是锁而不是OS原语.不要那样做.正确的锁需要使用内存屏障指令,潜在的缓存行刷新等等; 使用您的操作系统的原语进行锁定.这一点尤其重要,因为使用的原语可以在运行操作系统的各个CPU行之间进行更改; 什么在CPU Foo上工作可能不适用于CPU Foo2.大多数操作系统本身支持POSIX线程(pthreads)或提供它们作为OS线程包的包装器,因此通常最好用它们来说明示例.
如果您的操作系统提供了适当的原语,并且您绝对需要它来提高性能,那么您可以使用原子比较和交换操作来初始化共享的全局变量,而不是进行此类锁定/初始化.基本上,你写的东西看起来像这样:
MySingleton *MySingleton::GetSingleton() {
if (pObj == NULL) {
// create a temporary instance of the singleton
MySingleton *temp = new MySingleton();
if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
// if the swap didn't take place, delete the temporary instance
delete temp;
}
}
return pObj;
}
Run Code Online (Sandbox Code Playgroud)
这只适用于创建单个实例的多个实例(每个线程一个同时调用GetSingleton())然后抛出额外内容的情况.在OSAtomicCompareAndSwapPtrBarrier提供在Mac OS X功能-大多数操作系统都提供了类似的原始-检查是否pObj是NULL只有真正将其设置为temp给它,如果它是.这实际上使用硬件支持,字面上只执行一次交换并告诉它是否发生.
如果你的操作系统提供它在这两个极端之间的另一个工具是pthread_once.这使您可以设置一个仅运行一次的功能 - 基本上通过执行所有锁定/屏障/等操作.诡计 - 无论调用多少次或调用多少线程.
Der*_*ark 12
基本上,您要求同步创建单例,而不使用任何同步(先前构造的变量).一般来说,不,这是不可能的.您需要一些可用于同步的东西.
至于你的另一个问题,是的,可以保证在执行其他代码之前初始化可以静态初始化的静态变量(即不需要运行时代码).这使得可以使用静态初始化的互斥锁来同步单例的创建.
从2003年的C++标准修订版:
具有静态存储持续时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5).使用常量表达式进行零初始化和初始化统称为静态初始化; 所有其他初始化是动态初始化.具有使用常量表达式(5.19)初始化的静态存储持续时间的POD类型(3.9)的对象应在任何动态初始化发生之前初始化.在同一翻译单元的命名空间范围内定义并动态初始化的静态存储持续时间的对象应按其定义出现在翻译单元中的顺序进行初始化.
如果您知道在初始化其他静态对象期间将使用此单例,我认为您会发现同步不是问题.据我所知,所有主要编译器都在一个线程中初始化静态对象,因此在静态初始化期间线程安全.您可以将单例指针声明为NULL,然后在使用之前检查它是否已初始化.
但是,这假设您知道在静态初始化期间将使用此单例.标准也不保证这一点,因此如果您想要完全安全,请使用静态初始化的互斥锁.
编辑:克里斯建议使用原子比较和交换肯定会有效.如果可移植性不是问题(并且创建额外的临时单例不是问题),那么它是一个略低的开销解决方案.
Fre*_*abe 11
这是一个非常简单的懒惰构造的单例getter:
Singleton *Singleton::self() {
static Singleton instance;
return &instance;
}
Run Code Online (Sandbox Code Playgroud)
这是懒惰的,下一个C++标准(C++ 0x)要求它是线程安全的.事实上,我相信至少g ++以线程安全的方式实现了这一点.那么,如果这是你的目标编译器,或者你使用的编译器也以线程安全的方式实现了这一点(可能是较新的Visual Studio编译器吗?我不知道),那么这可能就是你所需要的.
有关此主题,另请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2513.html.
如果没有任何静态变量,则无法执行此操作,但是如果您愿意容忍一个静态变量,则可以使用Boost.Thread来实现此目的.阅读"一次性初始化"部分以获取更多信息.
然后在你的单例访问器函数中,用于boost::call_once构造对象,并返回它.
对于gcc,这很容易:
LazyType* GetMyLazyGlobal() {
static const LazyType* instance = new LazyType();
return instance;
}
Run Code Online (Sandbox Code Playgroud)
GCC将确保初始化是原子的.对于VC++,情况并非如此.:-(
这种机制的一个主要问题是缺乏可测试性:如果你需要在测试之间将LazyType重置为新的,或者想要将LazyType*更改为MockLazyType*,那么你将无法做到.鉴于此,通常最好使用静态互斥+静态指针.
另外,可能是一边:最好总是避免静态非POD类型.(指向POD的指针是正常的.)原因很多:正如您所提到的,初始化顺序未定义 - 也不是调用析构函数的顺序.因此,程序在尝试退出时最终会崩溃; 通常不是什么大不了的事,但是当你试图使用的探查器需要一个干净的退出时,有时候是一个showstopper.
| 归档时间: |
|
| 查看次数: |
23763 次 |
| 最近记录: |