code

사용하지 않고 C ++ 11에서 다중 스레드 안전 싱글 톤을 구현하는 방법

codestyles 2020. 10. 31. 09:43
반응형

사용하지 않고 C ++ 11에서 다중 스레드 안전 싱글 톤을 구현하는 방법


이제 C ++ 11에 다중 스레딩이 있으므로 뮤텍스를 사용하지 않고 지연 초기화 된 싱글 톤을 구현하는 올바른 방법이 무엇인지 궁금합니다 (성능상의 이유로). 나는 이것을 생각해 냈지만 tbh Im은 lockfree 코드를 작성하는 데별로 능숙하지 않으므로 더 나은 솔루션을 찾고 있습니다.

// 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()단지 테스트되어, 실제 싱글은 그 기능이 wouldnt한다.


C ++ 11에서는 수동 잠금이 필요하지 않습니다. 동시 실행은 정적 지역 변수가 이미 초기화 된 경우 대기합니다.

§6.7 [stmt.dcl] p4

변수가 초기화되는 동안 제어가 선언에 동시에 입력되면 동시 실행은 초기화 완료를 기다려야합니다.

따라서 간단한 static기능은 다음과 같습니다.

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

이것은 컴파일러가 표준의 해당 부분을 적절하게 구현하는 한 C ++ 11에서 잘 작동합니다.


물론, 진짜 정답은 단일 마침표를 사용 하지 않는 것입니다.


나에게 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.
}

IMHO, 싱글 톤을 구현하는 가장 좋은 방법은 "double-check, single-lock"패턴을 사용하는 것입니다.이 패턴은 C ++ 11에서 이식 가능하게 구현할 수 있습니다. Double-Checked Locking Is Fixed In 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.

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.

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

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.


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

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


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

#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 static word is enough in c++11 and greater the developer may have the necessity to get much more control over the instance creation.

참고URL : https://stackoverflow.com/questions/11711920/how-to-implement-multithread-safe-singleton-in-c11-without-using-mutex

반응형