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