ACE_Singleton 的 instance的实现:
template <class TYPE, class ACE_LOCK> TYPE *
ACE_Singleton<TYPE, ACE_LOCK>::instance (void)
{
ACE_TRACE ("ACE_Singleton<TYPE, ACE_LOCK>::instance");
ACE_Singleton<TYPE, ACE_LOCK> *&singleton =
ACE_Singleton<TYPE, ACE_LOCK>::instance_i ();
// Perform the Double-Check pattern...
if (singleton == 0)
{
if (ACE_Object_Manager::starting_up () ||
ACE_Object_Manager::shutting_down ())
{
// The program is still starting up, and therefore assumed
// to be single threaded. There's no need to double-check.
// Or, the ACE_Object_Manager instance has been destroyed,
// so the preallocated lock is not available. Either way,
// don't register for destruction with the
// ACE_Object_Manager: we'll have to leak this instance.
ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
}
else
{
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
// Obtain a lock from the ACE_Object_Manager. The pointer
// is static, so we only obtain one per ACE_Singleton
// instantiation.
static ACE_LOCK *lock = 0;
if (ACE_Object_Manager::get_singleton_lock (lock) != 0)
// Failed to acquire the lock!
return 0;
ACE_GUARD_RETURN (ACE_LOCK, ace_mon, *lock, 0);
if (singleton == 0)
{
#endif /* ACE_MT_SAFE */
ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
// Register for destruction with ACE_Object_Manager.
ACE_Object_Manager::at_exit (singleton, 0, typeid (TYPE).name ());
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
}
#endif /* ACE_MT_SAFE */
}
}
return &singleton->instance_;
}
double-check技术主要是解决线程安全问题,避免在初始化时多线程重入,导致instance被实例化两次。
使用Double Checked Locking模式带来的几点好处:
1、最小化加锁。通过实现两个flag检测,Double Checked Locking模式实现通常用例的优化。一旦flag被设置,第一个检测将保证后续的访问不要加锁操作。
2、防止竞争条件。对flag的第二个检测将保证临界区中的事件仅实现一次。
使用Double Checked Locking模式也将带来一个缺点:产生微妙的移植bug的潜能。这个微妙的移植问题能够导致致命的bug,如果使用Double Checked Locking模式的软件被移植到没有原子性的指针和正数赋值语义的硬件平台上。例如,如果一个instance_指针被用来作为Singleton实现的flag,instance_指针中的所有位(bit)必须在一次操作中完成读和写。如果将new的结果写入内存不是一个原子操作,其他的线程可能会试图读取一个不健全的指针,这将导致非法的内存访问。
在一些允许内存地址跨越对齐边界的系统上这种现象是可能的,因此每次访问需要从内存中取两次。在这种情况下,系统可能使用分离的字对齐合成flag,来表示instance_指针。
如果一个过于激进(aggressive)编译器通过某种缓冲手段来优化flag,或是移除了第二个flag==0检测,将带来另外的相关问题。下面使用volatile关键字来解决这个问题:
private:
static volatile long Flag_; // Flag is volatile.
使用volatile将保证编译器不会将flag缓冲到编译器,同时也不会优化掉第二次读操作。使用volatile关键字的言下之意是所有对flag的访问是通过内存,而不是通过寄存器。