通过惰性求值使 Meyers 的单例线程安全快速地
Make Meyers' Singleton thread safe and fast with lazy evaluation
所以我读了很多关于为什么这个实现不是线程安全的。但我没有找到答案,如何使它线程安全和快速?使其线程安全的变体是添加互斥(或者在某些情况下,只添加关键部分就足够了),但这会使该方法变得更慢。那么,有没有一种变体可以让这个代码线程安全快速,或者至少现在和添加互斥锁一样慢?
static Singleton& getInstance()
{
static Singleton singleton;
return singleton;
}
PS:是的,我也读了很多关于线程安全Singleton实现的文章。当我们使用Singleton指针作为类的成员时,问题是关于Singleton的这个特定实现,没有指针和new,并且使用惰性评估。
对于某些编译器,您所拥有的可能已经具有线程安全保证。如果你不关心代码的可移植性,并且它对你有效,那么就对它感到满意
如果您有可用的boost线程,您可以使用boost::call_once
进行初始化。这是线程安全的,并且只在第一次初始化时花费。
当然,你也可以通过初始化"Meyers"单例创建,即在创建访问它的线程之前第一次访问它,使其完全线程安全。如果你已经实现了很多这样的单例,请考虑这样做。
所有这些,即使是boost::call_once
也只适用于对象的创建。然而,如果由多个线程访问,它的访问可能需要单独的同步技术。
(顺便说一句,Meyers Effective C++的第47项提到了这个单例,这表明该标准的后续修订使其线程安全,并且后续编译器符合它,但它确实警告您,并非所有编译器都符合它)。
好吧,所以如果没有互斥,你根本无法做到这一点,但你可以快速生成互斥。
首先声明一个类来保存互斥锁,以及一个就绪标志(下面的InitMutex
)。当调用GrabMutex()
,而ready
是false
时,实际的互斥被捕获。当调用ReleaseMutex()
时,它根据GrabMutex()
发送的标志做正确的事情。在第一次通过之后,ready变为true,因为静态对象现在已经初始化,所以不需要抓取互斥对象。
现在,声明一个类,该类在构造函数中调用GrabMutex()
,在析构函数中调用ReleaseMutex(flag)
,并在本地保存标志(下面的InitMutexHolder
)。
现在,在您的常规getSingleton
函数中实例化该类。这将确保singleton初始化在第一次通过时被互斥,并且如果多个线程竞争,它们将在互斥上排队。但是,一旦初始化了singleton,ready
就会变为true,访问就会很快。在执行return theSingleton
之后,会神奇地调用析构函数,从而释放互斥对象(如果没有获取互斥对象,则不执行任何操作)。
但是,theSingleton()
函数中的代码主体没有改变,我们只在每个单例中添加了一个控制对象,并在调用中添加了管理线程安全的堆栈对象。
关于写障碍的注意事项:由于每当ready
为false时,代码都是安全的,并且ready
在对象初始化之前不能为true,因此假设写操作立即可见,代码总体上是安全的。但是,在ready=true
可见时可能存在延迟,因为在设置ready true之后不存在写入屏障。然而,由于ready=false
是保守的、安全的情况,因此在延迟期间仍然保持安全。
class InitMutex
{
public:
InitMutex() : ready(false) { }
bool GrabMutex()
{
if (!ready)
{
mutex.Grab();
return true;
}
else
{
return false;
}
}
void ReleaseMutex(bool flagFromGrabMutex)
{
if (flagFromGrabMutex)
{
mutex.Release();
ready = true;
}
}
Mutex mutex;
bool ready;
};
class InitMutexHolder
{
public:
InitMutexHolder(InitMutex & m)
: initMutex(m)
{
inMutex = initMutex.GrabMutex();
}
~InitMutexHolder()
{
initMutex.ReleaseMutex(inMutex);
}
private:
bool inMutex;
InitMutex & initMutex;
};
static InitMutex singletonMutex;
static Singleton & getSingleton()
{
InitMutexHolder mutexHolder(singletonMutex);
{
static Singleton theSingleton;
return theSingleton;
}
}
我的问题的答案似乎最好地表达在Fred Larson的评论中:
"快速,线程安全,懒惰-选择任意两个。"
- C++为线程工作动态地分割例程
- 初学者C++线程安全单例设计
- Posix 线程类和启动例程 (pthread)
- OpenBLAS 只为一个例程设置线程数
- 使用Boost将单线线程转换为多线程
- 多线和模板的单例比赛条件
- 双重检查单例线程的实现是否安全?
- 构造函数在不同线程中的静态单例类上调用两次
- 为什么我的单例实现两次启动?(一个进程,多个线程)
- 如何在不使用的情况下在 <mutex>C++11 中实现多线程安全单例
- 线程安全单例支架性能
- 是什么使单例线程不安全
- 为多线程环境实现单例的内存泄漏
- 我们是否需要互斥锁来访问 C++11 - 多线程中单例对象中的数据字段
- C++11 中的线程安全单例
- 通过惰性求值使 Meyers 的单例线程安全快速地
- 以下是单例实现线程安全的
- 有人能确认一下这是否是一个线程安全的单例实现吗?
- 传递字符串作为线程启动例程参数:c++
- 两个线程访问单例类 - 无法正常工作