Как реализовать многопоточный безопасный синглтон в 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 62

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 вам все равно нужно сделать это самостоятельно.

к сожалению, ничего не бывает просто. Элемент пример кода ссылка на шаблон выше не может быть вызвана из инициализации 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();
    }
};