C++ 从多线程获取单实例的安全方法

C++ 从多线程获取单实例的安全方法,c++,multithreading,C++,Multithreading,方法1 DataCenter* DataCenter::getInstance() { static DataCenter instance; return &instance; } 方法2 DataCenter* DataCenter::getInstance() { if (!m_instanceFlag) { m_instance = new DataCenter(); m_instanceFlag = tr

方法1

DataCenter* DataCenter::getInstance()
{
    static DataCenter instance;
    return &instance;    
}
方法2

DataCenter* DataCenter::getInstance()
{
    if (!m_instanceFlag)
    {
        m_instance = new DataCenter();
        m_instanceFlag = true;
    }    
    return m_instance;      
}
我正在进行多线程编程,数据中心将由多个线程访问。我曾经使用方法2来获取数据中心的实例,它工作得很好。但是我注意到我需要保护单例实例不被多线程调用

我的问题是,首先,我真的需要保护singleton实例吗?还是操作系统为我做了这些?第二个问题是,第一种方法是获得单例实例的正确方法吗


提前感谢…

您需要使用双重检查锁定机制,但它可能不是100%安全的

DataCenter* DataCenter::getInstance()
{
  if (m_instance == null)
  {
    // some sort of synchronization lock    //1
     {  

        if (m_instance == null)             //2
            m_instance  = new DataCenter(); //3 
    }
  }
  return m_instance ;
}
再解释一下:

线程1进入getInstance()方法

线程1在//1处进入同步块,因为实例为null

线程1被线程2抢占

线程2进入getInstance()方法

线程2试图获取//1处的锁,因为实例仍然为null。但是,由于线程1持有锁,因此线程2在//1处阻塞

线程2被线程1抢占

线程1执行,因为instance在//2处仍然为null,所以创建一个单例对象并将其引用分配给instance

线程1退出同步块并从getInstance()方法返回实例

线程1被线程2抢占

线程2获取//1处的锁并检查实例是否为null


因为实例是非null的,所以不会创建第二个单例对象,而是返回由线程1创建的单例对象

是的,你真的需要这样做。如果线程1检查实例标志并将其替换为线程2,然后线程2执行整个
getInstance()
方法,那么线程1将继续获取另一个实例

这是因为它已经在标志为false时检查了它,并且不会因为上下文切换而重新检查它

如果多个线程可能同时调用
getInstance()
,则需要保护将标记作为原子单元进行检查和设置的操作

当然,如果在任何其他线程启动之前从主线程调用
getInstance
,您可以不受保护地离开

您可能还想放弃使用单独标志变量的想法。您可以在加载时将实例设置为NULL,并仅使用该选项:

DataCenter* DataCenter::getInstance(){
    static DataCenter *m_instance = 0;
    // begin atomic unit
    if(m_instance == 0)
        m_instance = new DataCenter();
    // end atomic unit
    return m_instance;
}

1.你确实需要保护它,当然,即使你不这样做,操作系统也不会为你做。使用以下代码确保螺纹安全:

DataCenter* DataCenter::getInstance()
{
    MutexLocker locker(DataCenter::m_mutex);
    if(!m_instanceFlag)
    {
        m_instance = new DataCenter();
        m_instanceFlag = true;
    }
    return m_instance;
}
编辑:

其中
MutexLocker
是这样的:

class MutexLocker
{
    pthread_mutex_t &mutex;
    public:
    MutexLocker(pthread_mutex_t &mutex):mutex(mutex)
    {
        if(pthread_mutex_lock(&this->mutex)!=0)
            throw std::runtime_error("mutex locking filed");
    }
    ~MutexLocker(void)
    {
        if(pthread_mutex_unlock(&this->mutex)!=0)
            throw std::runtime_error("mutex unlocking filed");
    }
}
if (m_instance == NULL) {
    grab_mutex();
    if (m_instance == NULL)
        m_instance = new Whatsit();
    release_mutex();
}

return m_instance;

2.第一种方法看起来不错,但不是线程安全的。

正如人们在评论中提到的,双重检查锁不是线程安全的解决方案。您确实需要使用某种机制来序列化对资源的访问。联锁交换可能是最简单的方法之一

template <typename T>
class Singleton
{
  private:
    Singleton();
    ~Singleton();
    Singleton& operator=(const Singleton& item);

  protected:
    static volatile long m_locker;

    static T* GetPointer()
    {
      T* pTmp = NULL;
      try
      {
         static T var;
         pTmp = &var;
      }
      catch(...)
      {
         //assert(false);
         pTmp = NULL;
      }
      return pTmp;
    }

  public:
    static T* Get()
    {
       while(::InterlockedExchange(&m_locker, 1) != 0)
         ::SwitchToThread();

       T* pTmp = GetPointer();
       ::InterlockedExchange(&m_locker, 0);
       return pTmp;
    }
};

template <typename T>
  volatile long Singleton<T>::m_locker = 0;
模板
单件阶级
{
私人:
Singleton();
~Singleton();
单例和运算符=(常量单例和项目);
受保护的:
静态易失性长m_锁;
静态T*GetPointer()
{
T*pTmp=NULL;
尝试
{
静态T-var;
pTmp=&var;
}
捕获(…)
{
//断言(假);
pTmp=NULL;
}
返回pTmp;
}
公众:
静态T*Get()
{
while(::InterlocateExchange(&m_locker,1)!=0)
::SwitchToThread();
T*pTmp=GetPointer();
::联锁交换(&m_locker,0);
返回pTmp;
}
};
模板
易失性长单例::m_locker=0;

如果调用
getSingleton
也可能初始化singleton,则只需保护singleton访问,否则,多个线程可能会尝试同时创建它

互斥锁足以防止竞争条件,但是,对
getSingleton
的每个后续调用也必须获得锁,这会降低性能。如果这是一个问题,并且您可以处理额外的复杂性,那么通过允许多个线程创建singleton实例并确定保留哪个实例(下面内联的代码),展示了一种避免锁定的方法:

Widget*g_pwiddcached;
Widget*GetSingletonWidget()
{
Widget*pwid=g_pwiddcached;
如果(!pwid){
pwid=new(nothrow)Widget();
if(pwid){
Widget*pwidOld=重新解释
(联锁比较交换点删除)(
&重新解释铸造(g_pwidCached),
pwid,NULL);
如果(pwidOld){
delete pwid;//丢失竞争-销毁冗余副本
pwid=pwidOld;//使用旧的
}
}
}
返回pwid;
}
当然,这是特定于Windows的,但代码可以替换为特定于平台的联锁操作,而不改变其含义。(作为奖励:如果您在Windows上编写代码,您只需使用提供的API即可完成繁重的工作!)


请注意,singleton的构造函数不能有副作用,否则会得到意外的结果。(雷蒙德的书中有更多的细节。)

我想我最好给出我的答案

首先,“方法1”在C++0x上运行良好。根据第6.7(4)节(重点):

具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的所有块作用域变量的零初始化(8.5)在任何其他初始化发生之前执行。。。否则,在控件第一次通过其声明时初始化该变量;此类变量在初始化完成时被视为已初始化如果控件在初始化变量时同时输入声明,则并发执行应等待初始化完成。

因此,如果您有C++0x,“方法1”是实现线程安全单例的一种简单、正确且100%可移植的方法。(偶数p
if (m_instance == NULL) {
    grab_mutex();
    if (m_instance == NULL)
        m_instance = new Whatsit();
    release_mutex();
}

return m_instance;
if (m_instance == NULL) {
    Whatsit *p = new Whatsit();
    if (InterlockedCompareExchangePointer(&m_instance, p, NULL) != NULL)
        delete p;
}

return m_instance;