Подтвердить что ты не робот

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

Теперь, когда С++ 11 имеет многопоточность, мне было интересно, какой правильный способ реализовать ленивый инициализированный синглтон без использования мьютексов (по первичным причинам). Я придумал это, но tbh Im действительно не очень хорошо пишет код для блокировки, поэтому Im ищет некоторые лучшие решения.

// 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() предназначен только для тестирования, у реального синглтона не будет этой функции.

4b9b3361

Ответ 1

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

§6.7 [stmt.dcl] p4

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

Таким образом, просто используйте функцию static следующим образом:

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

Это будет нормально работать в С++ 11 (конечно, если компилятор правильно реализует эту часть стандарта).


Конечно, реальный правильный ответ - не использовать одиночный период.

Ответ 2

Для меня лучший способ реализовать синглтон с использованием С++ 11:

class Singleton {
 public:
  static Singleton& Instance() {
    // Since it 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

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

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

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

Я предполагаю, что семантика локально-статической инициализации на основе С++ 11 работает даже при вызове во время инициализации CRT.

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

Ответ 4

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

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

Семантика вашего initialize нарушена. Если вы попытаетесь описать/задокументировать поведение функции, вам понравится, и она будет описать реализацию, а не просто операцию. Документация, как правило, является простым способом двойной проверки дизайна/алгоритма: если вы в конечном итоге описываете, как, а не то, то вам нужно вернуться к дизайну. В частности, нет гарантии, что после завершения initialize объект фактически был инициализирован (только если возвращаемое значение true, а иногда, если оно false, но не всегда).

Ответ 5

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

Ответ 6

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

}

Эта версия не требует одновременного использования, если стандарт С++ 11 не гарантированно поддерживается на 100%. Он также предлагает гибкий способ создания экземпляра "собственного" экземпляра. Даже если волшебного статического слова достаточно в С++ 11 и выше, разработчику может потребоваться гораздо больший контроль над созданием экземпляра.