如何在c++ 11中不使用实现多线程安全单例

时间:2022-09-02 08:00:39

Now that C++11 has multithreading I was wondering what is the correct way to implement lazy initialized singleton without using mutexes(for perf reasons). I came up with this, but tbh Im not really good at writing lockfree code, so Im looking for some better solutions.

现在c++ 11已经有了多线程,我想知道在不使用互斥对象的情况下实现惰性初始化单例的正确方法是什么(出于以上原因)。我想到了这个,但是tbh我不太擅长写无锁代码,所以我在寻找更好的解决方案。

// 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;
}

Note that clear() is just for testing, real singleton wouldnt have that function.

请注意clear()只是用于测试,真正的singleton没有该函数。

5 个解决方案

#1


120  

C++11 removes the need for manual locking. Concurrent execution shall wait if a static local variable is already being initialized.

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

§6.7 [stmt.dcl] p4

§6.7[支撑。dcl]p4

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

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

As such, simple have a static function like this:

因此,simple有一个像这样的静态函数:

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

This will work all-right in C++11 (as long as the compiler properly implements that part of the standard, of course).

这在c++ 11中可以正常工作(当然,前提是编译器正确地实现了标准的这一部分)。


Of course, the real correct answer is to not use a singleton, period.

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

#2


27  

For me the best way to implement a singleton using C++11 is:

对于我来说,使用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.
}

#3


2  

It is hard to read your approach as you are not using the code as intended... that is, the common pattern for a singleton is calling instance() to get the single instance, then use it (also, if you really want a singleton, no constructor should be public).

很难理解您的方法,因为您没有按照预期使用代码……也就是说,单例对象的常见模式是调用instance()来获取单个实例,然后使用它(而且,如果您真的想要一个单例对象,则不应该使用任何构造函数)。

At any rate, I don't think that your approach is safe, consider that two threads try to acquire the singleton, the first one that gets to update the flag will be the only one initializing, but the initialize function will exit early on the second one, and that thread might proceed to use the singleton before the first thread got around to complete initialization.

无论如何,我不认为你的方法是安全的,考虑到两个线程尝试获取单例,第一个被更新标志将只有一个初始化,但在早期初始化函数将退出第二个,之前,线程可能继续使用单例第一个线程来完成初始化。

The semantics of your initialize are broken. If you try to describe / document the behavior of the function you will have some fun, and will end up describing the implementation rather than a simple operation. Documenting is usually a simple way to double check a design/algorithm: if you end up describing how rather than what, then you should get back to design. In particular, there is no guarantee that after initialize completes the object has actually been initialized (only if the returned value is true, and sometimes if it is false, but not always).

初始化的语义被破坏了。如果您试图描述/记录函数的行为,那么您将获得一些乐趣,并最终描述实现,而不是简单的操作。记录文档通常是检查设计/算法的一种简单方法:如果您最终描述的是如何而不是什么,那么您应该回到设计。特别是,不能保证在initialize完成之后,对象实际上已经被初始化(只有在返回值为true时,有时是false时,但并非总是如此)。

#4


2  

IMHO, the best way to implement singletons is with a "double-check, single-lock" pattern, which you can implement portably in C++ 11: Double-Checked Locking Is Fixed In C++11 This pattern is fast in the already-created case, requiring only a single pointer comparison, and safe in the first-use case.

在IMHO中,实现单例的最佳方式是使用“双检查、单锁”模式,您可以在c++ 11中实现这种模式:在c++ 11中,双检查锁定是固定的。

As mentioned in previous answer, C++ 11 guarantees construction-order safety for static local variables Is local static variable initialization thread-safe in C++11? so you are safe using that pattern. However, Visual Studio 2013 does not yet support it :-( See the "magic statics" row on this page, so if you are using VS2013 you still need to do it yourself.

如前所述,c++ 11保证了静态局部变量的构造顺序安全,那么c++ 11中的局部静态变量初始化线程安全吗?所以你使用这个模式是安全的。然而,Visual Studio 2013还不支持它:-(请参阅本页上的“magic statics”行,因此如果您正在使用VS2013,您仍然需要自己进行操作。

Unfortunately, nothing is ever simple. The sample code referenced for the pattern above cannot be called from CRT initialization, because the static std::mutex has a constructor, and is thus not guaranteed to be initialized before the first call to get the singleton, if said call is a side-effect of CRT initialization. To get around that, you have to use, not a mutex, but a pointer-to-mutex, which is guaranteed to be zero-initialized before CRT initialization starts. Then you would have to use std::atomic::compare_exchange_strong to create and use the mutex.

不幸的是,没有什么是简单的。由于静态std:::mutex有一个构造函数,所以不能从CRT初始化中调用上面的模式引用的示例代码,因此不能保证在第一次调用获取singleton之前进行初始化,如果这个调用是CRT初始化的副作用的话。为了解决这个问题,您必须使用,而不是互斥对象,而是互斥对象,在CRT初始化开始之前,互斥对象必须被保证为零初始化。然后必须使用std::atomic::compare_exchange_strong来创建和使用互斥对象。

I am assuming that the C++ 11 thread-safe local-static-initialization semantics work even when called during CRT initialization.

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

So if you have the C++ 11 thread-safe local-static-initialization semantics available, use them. If not, you have some work to do, even moreso if you want your singleton to be thread-safe during CRT initialization.

因此,如果您有c++ 11线程安全的本地静态初始化语义可用,那么使用它们。如果不是,您还有一些工作要做,如果您希望您的singleton在CRT初始化期间是线程安全的。

#5


0  

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 T);
        });
        return *m_ins.get();
    }
};

#1


120  

C++11 removes the need for manual locking. Concurrent execution shall wait if a static local variable is already being initialized.

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

§6.7 [stmt.dcl] p4

§6.7[支撑。dcl]p4

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

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

As such, simple have a static function like this:

因此,simple有一个像这样的静态函数:

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

This will work all-right in C++11 (as long as the compiler properly implements that part of the standard, of course).

这在c++ 11中可以正常工作(当然,前提是编译器正确地实现了标准的这一部分)。


Of course, the real correct answer is to not use a singleton, period.

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

#2


27  

For me the best way to implement a singleton using C++11 is:

对于我来说,使用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.
}

#3


2  

It is hard to read your approach as you are not using the code as intended... that is, the common pattern for a singleton is calling instance() to get the single instance, then use it (also, if you really want a singleton, no constructor should be public).

很难理解您的方法,因为您没有按照预期使用代码……也就是说,单例对象的常见模式是调用instance()来获取单个实例,然后使用它(而且,如果您真的想要一个单例对象,则不应该使用任何构造函数)。

At any rate, I don't think that your approach is safe, consider that two threads try to acquire the singleton, the first one that gets to update the flag will be the only one initializing, but the initialize function will exit early on the second one, and that thread might proceed to use the singleton before the first thread got around to complete initialization.

无论如何,我不认为你的方法是安全的,考虑到两个线程尝试获取单例,第一个被更新标志将只有一个初始化,但在早期初始化函数将退出第二个,之前,线程可能继续使用单例第一个线程来完成初始化。

The semantics of your initialize are broken. If you try to describe / document the behavior of the function you will have some fun, and will end up describing the implementation rather than a simple operation. Documenting is usually a simple way to double check a design/algorithm: if you end up describing how rather than what, then you should get back to design. In particular, there is no guarantee that after initialize completes the object has actually been initialized (only if the returned value is true, and sometimes if it is false, but not always).

初始化的语义被破坏了。如果您试图描述/记录函数的行为,那么您将获得一些乐趣,并最终描述实现,而不是简单的操作。记录文档通常是检查设计/算法的一种简单方法:如果您最终描述的是如何而不是什么,那么您应该回到设计。特别是,不能保证在initialize完成之后,对象实际上已经被初始化(只有在返回值为true时,有时是false时,但并非总是如此)。

#4


2  

IMHO, the best way to implement singletons is with a "double-check, single-lock" pattern, which you can implement portably in C++ 11: Double-Checked Locking Is Fixed In C++11 This pattern is fast in the already-created case, requiring only a single pointer comparison, and safe in the first-use case.

在IMHO中,实现单例的最佳方式是使用“双检查、单锁”模式,您可以在c++ 11中实现这种模式:在c++ 11中,双检查锁定是固定的。

As mentioned in previous answer, C++ 11 guarantees construction-order safety for static local variables Is local static variable initialization thread-safe in C++11? so you are safe using that pattern. However, Visual Studio 2013 does not yet support it :-( See the "magic statics" row on this page, so if you are using VS2013 you still need to do it yourself.

如前所述,c++ 11保证了静态局部变量的构造顺序安全,那么c++ 11中的局部静态变量初始化线程安全吗?所以你使用这个模式是安全的。然而,Visual Studio 2013还不支持它:-(请参阅本页上的“magic statics”行,因此如果您正在使用VS2013,您仍然需要自己进行操作。

Unfortunately, nothing is ever simple. The sample code referenced for the pattern above cannot be called from CRT initialization, because the static std::mutex has a constructor, and is thus not guaranteed to be initialized before the first call to get the singleton, if said call is a side-effect of CRT initialization. To get around that, you have to use, not a mutex, but a pointer-to-mutex, which is guaranteed to be zero-initialized before CRT initialization starts. Then you would have to use std::atomic::compare_exchange_strong to create and use the mutex.

不幸的是,没有什么是简单的。由于静态std:::mutex有一个构造函数,所以不能从CRT初始化中调用上面的模式引用的示例代码,因此不能保证在第一次调用获取singleton之前进行初始化,如果这个调用是CRT初始化的副作用的话。为了解决这个问题,您必须使用,而不是互斥对象,而是互斥对象,在CRT初始化开始之前,互斥对象必须被保证为零初始化。然后必须使用std::atomic::compare_exchange_strong来创建和使用互斥对象。

I am assuming that the C++ 11 thread-safe local-static-initialization semantics work even when called during CRT initialization.

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

So if you have the C++ 11 thread-safe local-static-initialization semantics available, use them. If not, you have some work to do, even moreso if you want your singleton to be thread-safe during CRT initialization.

因此,如果您有c++ 11线程安全的本地静态初始化语义可用,那么使用它们。如果不是,您还有一些工作要做,如果您希望您的singleton在CRT初始化期间是线程安全的。

#5


0  

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 T);
        });
        return *m_ins.get();
    }
};