Как реализовать многопоточный безопасный синглтон в C++11 без использования

теперь, когда C++11 имеет многопоточность, мне было интересно, Как правильно реализовать ленивый инициализированный синглтон без использования мьютексов (по причинам perf). Я придумал это, но 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() только для тестирования, реальный синглтон не имеют этой функции.

5 ответов


C++11 устраняет необходимость ручной блокировки. Параллельное выполнение должно ждать, если статическая локальная переменная уже инициализируется.

§6.7 [stmt.dcl] p4

Если элемент управления вводит объявление одновременно во время инициализации переменной, параллельное выполнение должно ждать завершения инициализации.

таким образом, просто есть


для меня лучший способ реализовать синглтон с помощью 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.
}

трудно прочитать ваш подход, поскольку вы не используете код по назначению... то есть общий шаблон для синглтона-это вызов instance() чтобы получить единственный экземпляр, используйте его (кроме того, если вы действительно хотите синглтон, ни один конструктор не должен быть общедоступным).

во всяком случае, я не думаю, что ваш подход безопасен, учтите, что два потока пытаются получить синглтон, первый, который получает обновление флага, будет единственным инициализирующим, но


IMHO, лучший способ реализовать синглеты - это шаблон" двойной проверки, с одной блокировкой", который вы можете реализовать переносимо в C++ 11: Двойная Блокировка Исправлена В C++11 Этот шаблон быстр в уже созданном случае, требуя только сравнения одного указателя, и безопасен в первом случае использования.

Как упоминалось в предыдущем ответе, C++ 11 гарантирует безопасность построения порядка для статических локальных переменных инициализация локальной статической переменной потокобезопасный в C++11? Так что вы в безопасности, используя этот шаблон. Однако Visual Studio 2013 еще не поддерживает его : - (см. строку "magic statics" на этой странице, поэтому, если вы используете VS2013, вам все равно нужно сделать это самостоятельно.

к сожалению, ничего не бывает просто. The пример кода ссылка на шаблон выше не может быть вызвана из инициализации CRT, потому что статический std::mutex имеет конструктор и, таким образом, не гарантируется инициализируется перед первым вызовом для получения синглтона, если указанный вызов является побочным эффектом инициализации CRT. Чтобы обойти это, вы должны использовать не мьютекс, а указатель на мьютекс, который гарантированно будет инициализирован нулем до начала инициализации CRT. Затем вам нужно будет использовать std::atomic:: compare_exchange_strong для создания и использования мьютекса.

Я предполагаю, что потокобезопасная локальная статическая семантика инициализации C++ 11 работает даже при вызове во время Инициализация ЭЛТ.

поэтому, если у вас есть потокобезопасная семантика локальной статической инициализации c++ 11, используйте их. Если нет, у вас есть работа, даже более того, если вы хотите, чтобы ваш синглтон был потокобезопасным во время инициализации CRT.


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