我编写了以下代码,创建了我的界面管理器的单例实例。
#include <intrin.h>
#pragma intrinsic(_ReadWriteBarrier)
boost::mutex global_interface_manager_creation_mutex;
interface_manager* global_interface_manager = NULL;
interface_manager* get_global_interface_manager() {
interface_manager* volatile temp = global_interface_manager;
_ReadWriteBarrier();
if (temp == NULL) {
boost::mutex::scoped_lock(global_interface_manager_creation_mutex);
temp = global_interface_manager;
if (temp == NULL) {
temp = new interface_manager();
_ReadWriteBarrier();
global_interface_manager = temp;
}
}
return temp;
}
但我不想使用锁和内存屏障,因此请将代码更改为:
interface_manager* get_global_interface_manager() {
interface_manager* volatile temp = global_interface_manager;
__assume(temp != NULL);
if (temp == NULL) {
temp = new interface_manager();
if(NULL != ::InterlockedCompareExchangePointer((volatile PVOID *)&global_interface_manager, temp, NULL)) {
delete temp;
temp = global_interface_manager;
}
}
return temp;
}
看起来这段代码效果不错,但我不确定,我真的不知道如何测试它是正确的。
答案 0 :(得分:1)
我的问题是:制作线程安全的单身人员真的,真的,真的有必要吗?
单身人士是值得商榷的,但他们确实有自己的用途(我想这些讨论会远远超出主题)。
然而,线程安全单例是99.99%的不必要时间,99.99%的时间实现错误(即使 知道如何做的人它在过去证明他们错了)。所以,我认为在这种情况下“你真的需要这个”是一个有效的问题。
如果在应用程序启动时创建单例实例,例如在main()中,则只有一个线程。这可以像调用get_global_interface_manager()一样简单,或者调用yourlibrary :: init()隐式调用get()。
一旦你这样做,任何关于线程安全的担忧都是无关紧要的,因为此时强制只有一个线程。并且,保证它将起作用。没有ifs和whens。
很多(如果不是全部)库都要求你在启动时调用init函数,因此这也不是一个不常见的要求。
答案 1 :(得分:1)
您是否考虑过使用pthread_once
? http://sourceware.org/pthreads-win32/manual/pthread_once.html
这是它的用例。
#include <stddef.h> // NULL
#include <pthread.h>
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static interface_manager* m;
static void* init_interface_manager()
{
m = new interface_manager;
return NULL;
}
interface_manager* get_global_interface_manager()
{
pthread_once(&once_control, &init_interface_manager);
return m;
}
答案 2 :(得分:0)
多线程编程的一个难点部分是99.9%的时间似乎有些工作,然后失败了。
在你的情况下,没有什么可以防止两个线程从全局指针返回NULL并且都分配新的单例。一个将被删除,但你仍然会将它作为函数的返回值传回。
我甚至无法说服自己,我自己的分析是正确的。
您可以通过返回global_interface_manager
而非temp
轻松修复此问题。仍然有可能创建一个你转过来并删除的interface_manager
,但我认为这是你的意图。
答案 3 :(得分:0)
您可以将单例存储为原子引用。实例化后,CAS设置引用。这不能保证两个副本不会被实例化,只能保证从inst返回相同的副本。由于标准C没有原子指令,我可以在Java中显示它:
class Foo
{
static AtomicReference<Foo> foo = new AtomicReference<>();
public static Foo inst()
{
Foo atomic = foo.get();
if(atomic != null)
return atomic;
else
{
Foo newFoo = new Foo();
//newFoo will only be set if no other thread has set it
foo.compareAndSet(null, newFoo);
//if the above CAS failed, foo.get would be a different object
return foo.get();
}
}
}