, я наткнулся на несколько озадачевшею меня реализацию Double-checked locking:
#include <atomic>
#include <mutex>
class Singleton {
public:
Singleton* GetInstance();
private:
Singleton() = default;
static std::atomic<Singleton*> s_instance;
static std::mutex s_mutex;
};
Singleton* Singleton::GetInstance() {
Singleton* p = s_instance.load(std::memory_order_acquire);
if (p == nullptr) {
std::lock_guard<std::mutex> lock(s_mutex);
p = s_instance.load(std::memory_order_relaxed); // <- этоif (p == nullptr) {
p = new Singleton();
s_instance.store(p, std::memory_order_release);
}
}
return p;
}
В принципе всё логично и работать оно будет, но какой в данном случае смысл в std::memory_order_relaxed под мьютексом? Мьютекс же работает как барьер и в любом случае чтение пройдет как std::memory_order_acquire? Или я что-то путаю?
KP>В принципе всё логично и работать оно будет, но какой в данном случае смысл в std::memory_order_relaxed под мьютексом? Мьютекс же работает как барьер и в любом случае чтение пройдет как std::memory_order_acquire? Или я что-то путаю?
Умение задавать вопросы в которых 100% ответа — это своего рода талант.
Здравствуйте, σ, Вы писали:
KP>>В принципе всё логично и работать оно будет, но какой в данном случае смысл в std::memory_order_relaxed под мьютексом? Мьютекс же работает как барьер и в любом случае чтение пройдет как std::memory_order_acquire? Или я что-то путаю?
σ>Умение задавать вопросы в которых 100% ответа — это своего рода талант.
KP>В принципе всё логично и работать оно будет, но какой в данном случае смысл в std::memory_order_relaxed под мьютексом? Мьютекс же работает как барьер и в любом случае чтение пройдет как std::memory_order_acquire? Или я что-то путаю?
Это оптимизация. memory_order_relaxed -- самый дешёвый вид чтения. На x86 это просто считывание переменной. Поскольку оно происходит под мьютексом, никаких дополнительных гарантий не нужно.
Здравствуйте, Шахтер, Вы писали:
Ш>Это оптимизация. memory_order_relaxed -- самый дешёвый вид чтения. На x86 это просто считывание переменной. Поскольку оно происходит под мьютексом, никаких дополнительных гарантий не нужно.
Я немного о другом. Разве мьютекс не является одновременно барьером? Если он является таковым, то memory_order_relaxed в данном случае не вносит никакой оптимизации.
Если же утверждение выше не верно, то хотелось бы понять каким образом memory_order_relaxed привносит здесь оптимизацию?
Здравствуйте, kaa.python, Вы писали:
KP>Здравствуйте, Шахтер, Вы писали:
Ш>>Это оптимизация. memory_order_relaxed -- самый дешёвый вид чтения. На x86 это просто считывание переменной. Поскольку оно происходит под мьютексом, никаких дополнительных гарантий не нужно.
KP>Я немного о другом. Разве мьютекс не является одновременно барьером? Если он является таковым, то memory_order_relaxed в данном случае не вносит никакой оптимизации.
KP>Если же утверждение выше не верно, то хотелось бы понять каким образом memory_order_relaxed привносит здесь оптимизацию?
Я не понимаю, чего здесь не понятного? Для считывания атомика нужно вызвать load с флагом. Флаг определяет дополнительные телодвижения, которые компилятор сгенерирует для соблюдения соответствующих гарантий.
Самый дешёвый флаг memory_order_relaxed. Он даёт только одну гарантию -- атомарность операции.
, я наткнулся на несколько озадачевшею меня реализацию Double-checked locking:
По мотивам подобного срача рефакторинга окаменелых говен, хотелось бы задать публике вопрос — имеет ли подобный ментальный онанизм красивый паттерн вообще какой-то стратегический смысл?
Постановка вопроса подразумевает, что объект под синглтоном делает что-то полезное и используется из разных потоков. Но делающий что-то полезное объект в данном случае должен сам обеспечивать потокобезопасность своих методов. Что в большинстве случае означает, явный или неявный lock внутри делающих что-то полезное методов.
Здравствуйте, landerhigh, Вы писали:
L>Короче, за что боремся?
Да меня сама конструкция заинтересовала, вариант использования std::memory_order_relaxed. Так что можно считать это не вопросом про синглтон, а вопросом про std::memory_order_relaxed и мьютексы
Здравствуйте, kaa.python, Вы писали:
KP>В принципе всё логично и работать оно будет, но какой в данном случае смысл в std::memory_order_relaxed под мьютексом? Мьютекс же работает как барьер и в любом случае чтение пройдет как std::memory_order_acquire? Или я что-то путаю?
Acquire был раньше, когда мьютекс захватывался. А теперь можно, пока под мьютексом, прочесть максимально дешёвым и быстрым образом и не получить за это по рыжей морде каким-нибудь обгоном. Поэтому — relaxed.
Иначе бы получалось, что мьютекс уже захвачен, и снова вдруг подымается машина сериализации операций — а нафига собственно?
$>Здравствуйте, Шахтер, Вы писали:
Ш>>Я не понимаю, чего здесь не понятного? Для считывания атомика нужно вызвать load с флагом.
$>Почему бы обычный volatile не использовать? Ведь буков меньше и на до- C++11 компиляторах соберётся.
Не ожидал такого вопроса в 2019 году... Рекомендую обратиться к первоисточникам.
Здравствуйте, Vamp, Вы писали:
V>$>Почему бы обычный volatile не использовать? Ведь буков меньше и на до- C++11 компиляторах соберётся. V>Не ожидал такого вопроса в 2019 году... Рекомендую обратиться к первоисточникам.
Безотносительно того, что volatile тут не поможет, хотелось бы заметить, что по коду сразу не понятно в каком году он написан, но судя по тому что в нем все еще гуляют голые указатели, а не std::unique_ptr — явно раньше 11-го года
И вообще, в С++11 уже давно появился std::call_once, который без лишних вопросов, должен решить проблему создания одного объекта.
Здравствуйте, netch80, Вы писали:
N>Acquire был раньше, когда мьютекс захватывался. А теперь можно, пока под мьютексом, прочесть максимально дешёвым и быстрым образом и не получить за это по рыжей морде каким-нибудь обгоном. Поэтому — relaxed. N>Иначе бы получалось, что мьютекс уже захвачен, и снова вдруг подымается машина сериализации операций — а нафига собственно?
а где нибудь можно про это почитать в систематизированном виде ?
те не в викпедии
Здравствуйте, sergey2b, Вы писали:
S>Здравствуйте, netch80, Вы писали:
N>>Acquire был раньше, когда мьютекс захватывался. А теперь можно, пока под мьютексом, прочесть максимально дешёвым и быстрым образом и не получить за это по рыжей морде каким-нибудь обгоном. Поэтому — relaxed. N>>Иначе бы получалось, что мьютекс уже захвачен, и снова вдруг подымается машина сериализации операций — а нафига собственно?
S>а где нибудь можно про это почитать в систематизированном виде ? S>те не в викпедии
$>Здравствуйте, netch80, Вы писали:
N>>До C++11 можно было платформенно-зависимые функции применять.
$>Ага, InterlockedExchange. Подзабыл уже.
При чём тут это? InterlockedExchange не имеет уже никакого смысла, если мы под мьютексом, который защищает доступ к переменной (и все участники соблюдают это). Он нужен, если мы стараемся обойти без мьютекса — но в этом случае в C++11 есть свой atomic_exchange.
Про платформенно-зависимые я имел в виду, что где-то pthread_mutex_lock, где-то WaitForSingleObject над мьютексом.
А вы таки путаетесь в самых основах, лучше повторить.
$>На Java достаточно volatile.
И снова мимо. Явовский volatile ближе всего к std::atomic<> load/store с memory_order_seq_cst.
Но он не даст защиты больше чем на одно чтение/запись. Аналог доступа под мьютексом — это synchronized.
$>Здравствуйте, Шахтер, Вы писали:
Ш>>Я не понимаю, чего здесь не понятного? Для считывания атомика нужно вызвать load с флагом.
$>Почему бы обычный volatile не использовать? Ведь буков меньше и на до- C++11 компиляторах соберётся.
volatile это всего лишь директива компилятору не оптимизировать доступ к переменной, т.е. не кешировать
int a = 1;
while (a){}
компилятор может соптимизировать в регистры или стек и сделать
int b = a;
while (b){}
очевидно что с другого потока если изменить "a" то первый не увидит у себя в цикле изменения
а вот гарантии в каком порядке CPU изменят и увидят эти изменения "a" уже дает atomic
на всяких x86 платформах это не имеет особо значения
а вот на arm,ppc уже ой как актуально
а гарантии того что этот "a" не изменится другим треидом в ОС дают мютексы
иногда вместо мютексов используют синхронизации на atomic-ах, называется это уже lockfree программирование
Здравствуйте, netch80, Вы писали:
N>>>До C++11 можно было платформенно-зависимые функции применять. N>$>Ага, InterlockedExchange. Подзабыл уже.
N>При чём тут это? InterlockedExchange не имеет уже никакого смысла, если мы под мьютексом, который защищает доступ к переменной (и все участники соблюдают это). Он нужен, если мы стараемся обойти без мьютекса — но в этом случае в C++11 есть свой atomic_exchange. N>Про платформенно-зависимые я имел в виду, что где-то pthread_mutex_lock, где-то WaitForSingleObject над мьютексом.
N>А вы таки путаетесь в самых основах, лучше повторить.
Really? А вам не мешало бы ещё раз перечитать ваше сообщение, на которое ч отвечал, и double check locking.
N>$>На Java достаточно volatile.
N>И снова мимо. Явовский volatile ближе всего к std::atomic<> load/store с memory_order_seq_cst. N>Но он не даст защиты больше чем на одно чтение/запись. Аналог доступа под мьютексом — это synchronized.
Здравствуйте, $$, Вы писали:
N>>А вы таки путаетесь в самых основах, лучше повторить.
$>Really? А вам не мешало бы ещё раз перечитать ваше сообщение, на которое ч отвечал, и double check locking.
Really? Реализацию синглтона на одном только InterlockedExchange — в студию.
N>>$>На Java достаточно volatile. N>>И снова мимо. Явовский volatile ближе всего к std::atomic<> load/store с memory_order_seq_cst. N>>Но он не даст защиты больше чем на одно чтение/запись. Аналог доступа под мьютексом — это synchronized.
$>
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, $$, Вы писали:
N>>>А по сути?
N>$>Попробуйте включить голову: до C++ 11 кто-то писал double check locking.
N>$>T* temp = 0; N>$>InterlockedEchangePointer(&s_value, &temp); N>$>T* value = temp; N>$>InterlockedEchangePointer(&s_value, &temp); N>$>if(!value) { N>$>acquireMutex... N>$>}
N>Вот именно, что acquireMutex. Всё равно без мьютекса не обошлись. И кому тут надо было включать голову, если в упор не видите в собственном коде?
Мьютекс можно заменить на спинлок, который реализуется на InterlockedIncrement/Decrement. Но это, конечно, извращение.