Дважды проверил блокировку в C++11?
Вот пример проблемы с Java на http://www.ibm.com/developerworks/java/library/j-dcl/index.html
public static Singleton getInstance()
{
if (instance == null) //#4
{
synchronized(Singleton.class) { //#1
if (instance == null) //#2
instance = new Singleton(); //#3
}
}
return instance;
}
Кажется, это небезопасно, потому что #3 может установить экземпляр равным NULL до того, как конструктор будет выполнен, поэтому, когда другой поток проверяет экземпляр на #4, он не будет иметь значение NULL и вернет экземпляр, который не был сконструирован должным образом.
Очевидно, использование переменной функции не поможет, потому что она может быть оптимизирована или просто выполнена таким образом, что также устанавливает значение в экземпляр, когда мы этого не хотим.
Я думал, что не самый простой способ, чтобы иметь функцию new Singleton();
поэтому он завершен, прежде чем назначить его экземпляру. Теперь вопрос в том, как сказать C++, что функция НЕ должна быть встроенной? Я думаю Singleton* make_singleton() volatile
должен сделать это, но я уверен, что я не прав.
2 ответа
Некоторое время я буду игнорировать одноэлементные биты и предположить, что вам это нужно для ленивой инициализации, а не для глупых вещей, таких как синглтоны.
Я предлагаю забыть о двойной проверке блокировки. C++ предоставляет очень полезный инструмент для такого рода ситуаций в виде std::call_once
так что используйте это.
template <typename T>
struct lazy {
public:
// needs constraining to prevent from doing copies
// see: http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html
template <typename Fun>
explicit lazy(Fun&& fun) : fun(std::forward<Fun>(fun)) {}
T& get() const {
std::call_once(flag, [this] { ptr.reset(fun()); });
return *ptr;
}
// more stuff like op* and op->, implemented in terms of get()
private:
std::once_flag flag;
std::unique_ptr<T> ptr;
std::function<T*()> fun;
};
// --- usage ---
lazy<foo> x([] { return new foo; });
Это как раз тот тип ситуации, для которой предназначены атомики. Сохраняя результат в атомарном, вы знаете, что компилятор не может упорядочить любые критические хранилища или операции после того, как атомарное задано. Атомика предназначена как для выдачи примитивов инструкций процессора, чтобы обеспечить необходимую последовательную согласованность (например, для когерентности кэша на ядрах), так и для сообщения компилятору, какая семантика должна быть сохранена (и, следовательно, для ограничения типов переупорядочений, которые он может выполнять). Если вы используете здесь атомарный элемент, не имеет значения, является ли функция встроенной, потому что независимо от того, что делает встроенный компилятор, он должен сохранять семантику самого атомарного элемента.
Вы также можете быть заинтересованы в изучении std::call_once
который также предназначен для этой ситуации, а более конкретно для ситуации, когда многим потокам может понадобиться что-то сделать, но это должен делать только один из них.