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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11711920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:27:25  来源:igfitidea点击:

How to implement multithread safe singleton in C++11 without using <mutex>

c++multithreadingc++11singletonatomic

提问by NoSenseEtAl

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 具有多线程,我想知道在不使用互斥体的情况下实现延迟初始化单例的正确方法是什么(出于性能原因)。我想出了这个,但是我不太擅长编写无锁代码,所以我正在寻找一些更好的解决方案。

// 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()仅用于测试,真正的单身人士不会具有该功能。

回答by Xeo

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 [stmt.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 staticfunction like this:

因此, simple 有一个static这样的功能:

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 realcorrect answer is to notuse a singleton, period.

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

回答by GutiMac

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.
}

回答by Wheezil

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++11This pattern is fast in the already-created case, requiring only a single pointer comparison, and safe in the first-use case.

恕我直言,实现单例的最佳方法是使用“双重检查,单锁”模式,您可以在 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 codereferenced 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.

不幸的是,没有什么是简单的。不能从 CRT 初始化中调用上面模式引用的示例代码,因为静态 std::mutex 有一个构造函数,因此不能保证在第一次调用之前初始化以获取单例,如果所述调用是一个侧面 - 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 线程安全本地静态初始化语义,请使用它们。如果没有,您还有一些工作要做,如果您希望单例在 CRT 初始化期间是线程安全的,则更是如此。

回答by Pooja Kuntal

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();
    }
};

回答by David Rodríguez - dribeas

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 initializefunction will exit early on the second one, and that thread might proceed to use the singleton beforethe first thread got around to complete initialization.

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

The semantics of your initializeare broken. If you try to describe/ documentthe 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 howrather than what, then you should get back to design. In particular, there is no guarantee that after initializecompletes the object has actually been initialized (only if the returned value is true, and sometimes if it is false, but not always).

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

回答by Michal Turlik

#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;

}

This version complies to be concurrent free where c++11 standard is not guaranteed to be 100% supported. It offers also a flexible way to instantiate the "owned" instance. Even if the magic staticword is enough in c++11 and greater the developer may have the necessity to get much more control over the instance creation.

此版本符合并发免费,但不能保证 100% 支持 C++11 标准。它还提供了一种灵活的方式来实例化“拥有的”实例。即使神奇的静态词在 c++11 和更高版本中就足够了,开发人员可能需要更多地控制实例创建。