如何在不使用<mutex>的情况下在C ++ 11中实现多线程安全单例


71

既然C ++ 11具有多线程功能,我想知道在不使用互斥对象的情况下实现延迟初始化单例的正确方法是什么(出于性能原因)。我想到了这一点,但是tbh Im并不太擅长编写无锁代码,因此Im正在寻找更好的解决方案。

// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
# include <atomic>
# include <thread>
# include <string>
# include <iostream>
using namespace std;
class Singleton
{

public:
    Singleton()
    {
    }
static  bool isInitialized()
    {
        return (flag==2);
    }
static  bool initizalize(const string& name_)
    {
        if (flag==2)
            return false;// already initialized
        if (flag==1)
            return false;//somebody else is initializing
        if (flag==0)
        {
            int exp=0;
            int desr=1;
            //bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr)
            bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr);
            if (! willInitialize)
            {
                //some other thread CASed before us
                std::cout<<"somebody else CASed at aprox same time"<< endl;
                return false;
            }
            else 
            {
                initialize_impl(name_);
                assert(flag==1);
                flag=2;
                return true;
            }
        }
    }
static void clear()
{
    name.clear();
    flag=0;
}
private:
static  void initialize_impl(const string& name_)
{
        name=name_;
}
static  atomic<int> flag;
static  string name;
};
atomic<int> Singleton::flag=0;
string Singleton::name;
void myThreadFunction()
{
    Singleton s;
    bool initializedByMe =s.initizalize("1701");
    if (initializedByMe)
        s.clear();

}
int main()
{
    while (true)
    {
        std::thread t1(myThreadFunction);
        std::thread t2(myThreadFunction);
        t1.join();
        t2.join();
    }
    return 0;
}

请注意,这clear()只是为了测试,真正的单例将不会具有该功能。


Answers:


153

C ++ 11消除了手动锁定的需要。如果静态局部变量已经被初始化,则并发执行应等待。

§6.7 [stmt.dcl] p4

如果在初始化变量的同时控件同时输入了声明,则并发执行应等待初始化完成。

因此,simple具有如下static功能:

static Singleton& get() {
  static Singleton instance;
  return instance;
}

这将在C ++ 11中正常运行(当然,只要编译器正确地实现了标准的那一部分)。


当然,真正的正确答案是不要使用单例句号。


4
当然,不能保证实现不会使用mutices来确保行为,因此“由于性能原因而没有mutices”可能无法由此得到满足(Singleton初始化是否是担心的正确地方,这当然值得商bat )
灰熊

4
@Grizzly:好吧,它不“使用<mutex>”。;)
Xeo 2012年

7
+1,如果仅适用于Of course, the real correct answer is to not use a singleton, period... :-) ...
paercebal 2012年

3
"Of course, the real correct answer is to not use a singleton, period."-我必须不同意,单身人士有目的,他们过度使用和滥用很多,但他们有目的。
Leandros

2
@Leandros那会是哪一个?

44

对我而言,使用C ++ 11实现单例的最佳方法是:

class Singleton {
 public:
  static Singleton& Instance() {
    // Since it's a static variable, if the class has already been created,
    // it won't be created again.
    // And it **is** thread-safe in C++11.
    static Singleton myInstance;

    // Return a reference to our instance.
    return myInstance;
  }

  // delete copy and move constructors and assign operators
  Singleton(Singleton const&) = delete;             // Copy construct
  Singleton(Singleton&&) = delete;                  // Move construct
  Singleton& operator=(Singleton const&) = delete;  // Copy assign
  Singleton& operator=(Singleton &&) = delete;      // Move assign

  // Any other public methods.

 protected:
  Singleton() {
    // Constructor code goes here.
  }

  ~Singleton() {
    // Destructor code goes here.
  }

 // And any other protected methods.
}

保护区中有〜Singleton()?可以进入吗?
snb

3
static Singleton myInstance;应该在实现中定义,而不是在标头中定义。
杰里米

为什么使构造函数受保护而不是私有?派生类将有权访问Singleton类构造函数,并创建另一个Singleton对象。
bigdata2'4

4

恕我直言,实现单例的最佳方法是使用“双重检查,单锁”模式,您可以在C ++ 11中移植该模式: 在C ++ 11中固定了双重检查锁定 该模式创建案例,只需要一个指针比较,并且在首次使用案例中是安全的。

如先前的答案中所述,C ++ 11保证静态局部变量的构造顺序安全性在C ++ 11中局部静态变量初始化线程安全吗?因此您可以安全使用该模式。但是,Visual Studio 2013尚不支持它:-(请参阅此页上的“魔术静态”行,因此,如果您使用的是VS2013,则仍然需要自己进行操作。

不幸的是,没有什么比这更简单了。不能从CRT初始化中调用上述模式引用的示例代码,因为静态std :: mutex具有构造函数,因此,如果所述调用是单向调用,则不能保证在第一次调用之前获得初始化。 CRT初始化的效果。为了解决这个问题,您必须使用互斥锁,而不是互斥锁,而必须保证在CRT初始化开始之前将其初始化为零。然后,您将必须使用std :: atomic :: compare_exchange_strong创建并使用互斥量。

我假设即使在CRT初始化期间调用C ++ 11线程安全的局部静态初始化语义也能正常工作。

因此,如果您具有C ++ 11线程安全的本地静态初始化语义,请使用它们。如果不是这样,则您需要做一些工作,如果您希望单例在CRT初始化期间是线程安全的,则还有更多工作要做。


3
template<class T> 
class Resource
{
    Resource<T>(const Resource<T>&) = delete;
    Resource<T>& operator=(const Resource<T>&) = delete;
    static unique_ptr<Resource<T>> m_ins;
    static once_flag m_once;
    Resource<T>() = default;

public : 
    virtual ~Resource<T>() = default;
    static Resource<T>& getInstance() {
        std::call_once(m_once, []() {
            m_ins.reset(new Resource<T>);
        });
        return *m_ins.get();
    }
};

1
我不清楚的一件事。为什么将析构函数声明为虚拟的?如果构造函数是私有的,那么如何从单例继承?
irsis

2

很难理解您的方法,因为您没有按预期使用代码……也就是说,单例的常见模式是调用instance()获取单个实例,然后使用它(此外,如果您确实想要单例,则不构造函数应该是公共的)。

无论如何,我认为您的方法并不安全,请考虑两个线程尝试获取单例,第一个获得更新标志的线程将是唯一一个初始化,但是该initialize函数将在第二个提早退出一个,并且该线程可能第一个线程完成初始化之前继续使用单例。

您的语义initialize已损坏。如果您试图描述/记录功能的行为,您将会很有趣,并且最终将描述实现而不是简单的操作。记录文档通常是一种仔细检查设计/算法的简单方法:如果最终描述的是方法 而不是内容,那么您应该重新设计。特别地,不能保证initialize对象完成后实际上已经被初始化(仅当返回值为true,有时是false,但并非总是如此)。


代码很丑陋,但是请不要初始化返回布尔值。因此,调用方知道是否已初始化,如果未初始化,则可以在isInitialized上自旋
NoSenseEtAl

2
#pragma once

#include <memory>
#include <mutex>

namespace utils
{

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

    Singleton<T>() = default;

    static std::unique_ptr<T> m_instance;
    static std::once_flag m_once;

public:
    virtual ~Singleton<T>() = default;

    static T* getInstance()
    {
        std::call_once(m_once, []() {
            m_instance.reset(new T);
        });
        return m_instance.get();
    }

    template<typename... Args>
    static T* getInstance2nd(Args&& ...args)
    {
        std::call_once(m_once, [&]() {
            m_instance.reset(new T(std::forward<Args>(args)...));
        });
        return m_instance.get();
    }
};

template<typename T> std::unique_ptr<T> Singleton<T>::m_instance;
template<typename T> std::once_flag Singleton<T>::m_once;

}

如果不保证100%支持c ++ 11标准,则此版本符合并发免费要求。它还提供了一种灵活的方式来实例化“拥有的”实例。即使在c ++ 11和更高版本中魔术静态词就足够了,开发人员也可能有必要对实例创建进行更多控制。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.