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