Здравствуйте, tdiff, Вы писали:
T>Если не ошибаюсь, в стандарте ++ его вообще нет, так что каждый компилятор ведёт себя, как захочется.
Ошибаешься.
7.1.6.1/7
volatile is a hint to the implementation to avoid aggressive optimization involving the object
because the value of the object might be changed by means undetectable by an implementation. See 1.9 for
detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are
in C.
Здравствуйте, dead0k, Вы писали:
D>Здравствуйте, Lazin, Вы писали: L>>В boost/shared_ptr/detail есть простой spin_lock, который полностью header-only и построен на атомарных операциях и барьерах. D>Можно ссылку? D>Я просто всегда думал, что атомарные операции и барьеры суть std/crt. Т.е. все равно вызов неких библиотечных функций
нет, это либо ассемблерные вставки, либо интринсики компилятора, посмотреть можно в реализации boost/shared_ptr/detail/spin_lock.h
Здравствуйте, tdiff, Вы писали:
T>Возьмём классический пример синхронизации потоков:
{...}
T>Подобный код приводится в качестве примера ошибочного, т.к. может быть оптимизирован компилятором до
{...} T>Такая оптимизация возможна, т.к. компилятор видит, что раз внутри work() flag не меняется, поэтому T>нет смысла считывать его на каждой итерации.
T>Стандартное в практике решение этой проблемы — использовать mutex:
{...} T>Моё предположение такое: где-то внутри mutex используется какая-то специальная инструкция типа memory barrier, которую компилятор не может позволить себе отоптимизировать. T>Так ли это? Если да, то что это за инструкция на самом деле?
Насколько я знаю то что Вы написали в качестве "правильного" варианта тоже неверно. Обычно рекомендуется использовать специальные объекты ядра — события, семафоры, мьютексы (я проглядел по диагонали, по моемы Вы под мьютексом понимаете нечто другое). Когда Вы синхронизируетесь с помощью объекта ядра Ваш ждущий поток не крутится в цикле, поедая драгоценное время, а выключается из планировщика потоков и сразу же ставится в очередь как только объект сигнализирует о том, что ожидание закончилось.
То есть схема такая:
//Вы создаете событие в обоих синхронизируемых процессах.
HANDLE hEv=CreateEvent(NULL,FALSE,TRUE,"lalala");
//затем один процесс "засыпает" до окончания работы второго:
WaitForSingleObject(hEv);
//и делает что то дальше
//**********************************
//второй процесс чтото делает
{...}
//по окончании говорит, что момент пришел:
SetEvent(hEv) //именно в этот момент "проснется", то есть вернется из функции WaitForSingleObject первый процесс.
Компилятор, естественно, там ничего оптимизировать и испортить не сможет.
Здравствуйте, tdiff, Вы писали:
T>Возьмём классический пример синхронизации потоков:
T>
T>bool flag = false;
T>void thread_1()
T>{
T> while (!flag) {
T> work();
T> }
T>}
T>void thread_2()
T>{
T> sleep(1000);
T> flag = true;
T>}
T>
T>В функции work() flag не меняется.
T>Подобный код приводится в качестве примера ошибочного, т.к. может быть оптимизирован компилятором до T> ...
Нет. Этот код является ошибочным, потому что тут есть race condition, наличие которого приводит к UB (undefined behavior).
если есть UB, уже не важно что именно сгенерит компилятор.
Какие-то люди писали выше что достаточно написать volatile. Нет. Volatile не убирает race condition, код по прежнему будет приводить к UB.
Race condition возникает когда "a program ... contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other".
"conflicting actions" — это когда одно из действий — запись.
"happens before" — это когда либо действия выполняются в одном потоке, либо в разных но с мьютексом.
По этому правильные решения — это либо использовать атомарный тип (например std::atomic<bool>), либо использовать мьютекс.
A>Нет. Этот код является ошибочным, потому что тут есть race condition, наличие которого приводит к UB (undefined behavior). A>если есть UB, уже не важно что именно сгенерит компилятор.
A>Какие-то люди писали выше что достаточно написать volatile. Нет. Volatile не убирает race condition, код по прежнему будет приводить к UB.
A>Race condition возникает когда "a program ... contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other". A>"conflicting actions" — это когда одно из действий — запись. A>"happens before" — это когда либо действия выполняются в одном потоке, либо в разных но с мьютексом.
A>По этому правильные решения — это либо использовать атомарный тип (например std::atomic<bool>), либо использовать мьютекс.
Здравствуйте, Lazin, Вы писали:
A>>Нет. Этот код является ошибочным, потому что тут есть race condition, наличие которого приводит к UB (undefined behavior). A>>если есть UB, уже не важно что именно сгенерит компилятор.
A>>Какие-то люди писали выше что достаточно написать volatile. Нет. Volatile не убирает race condition, код по прежнему будет приводить к UB.
A>>Race condition возникает когда "a program ... contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other". A>>"conflicting actions" — это когда одно из действий — запись. A>>"happens before" — это когда либо действия выполняются в одном потоке, либо в разных но с мьютексом.
A>>По этому правильные решения — это либо использовать атомарный тип (например std::atomic<bool>), либо использовать мьютекс.
L> вопрос же не об этом был
это гадания о том, нужен volatile или нет, вместо того чтобы заглянуть в стандарт, и поискать упоминания volatile в [intro.multithread].
A> это гадания о том, нужен volatile или нет, вместо того чтобы заглянуть в стандарт, и поискать упоминания volatile в [intro.multithread].
Начнем с того, что у ТС-а ни слова нет о новом стандарте, поэтому, рассказывать о том, что тут race и UB — неправильно, в старом стандарте нет ни слова о многопоточности и любая многопоточная программа это UB и что дальше? Продолжить можно тем, что ТС-а интересовало то, почему оптимизации компилятора не ломают код, вопрос интересный, может лучше было найти в стандарте что-нибудь об этом? Я может быть резковат, но это потому, что меня уже порядком достали пуристы от стандартизации. Это С++, детка, UB это не всегда плохо
Здравствуйте, Lazin, Вы писали:
A>> это гадания о том, нужен volatile или нет, вместо того чтобы заглянуть в стандарт, и поискать упоминания volatile в [intro.multithread].
L>Начнем с того, что у ТС-а ни слова нет о новом стандарте,
новый это С++14 или тот которому уже несколько лет?
L>Я может быть резковат, но это потому, что меня уже порядком достали пуристы от стандартизации. Это С++, детка, UB это не всегда плохо
а меня достали говнокодеры которые пишут говно с UB, которое перестает работать в новых версиях компилятора, или не работает с другими компиляторами.
еще меня достали всякие отсталые, которые живут в нулевых, а то и в 90х.
Здравствуйте, Abyx, Вы писали:
L>>Начнем с того, что у ТС-а ни слова нет о новом стандарте, A>новый это С++14 или тот которому уже несколько лет?
тот, которому несколько лет
L>>Я может быть резковат, но это потому, что меня уже порядком достали пуристы от стандартизации. Это С++, детка, UB это не всегда плохо A>а меня достали говнокодеры которые пишут говно с UB, которое перестает работать в новых версиях компилятора, или не работает с другими компиляторами.
ну значит код std::atomic писали такие люди, так как там — UB, если например сделать так:
то там в шаблоне переменной просто значение присвоится и все, прямо как у топикстартера в примере, ну то есть по сути, store(value, memory_order_relaxed) — не более чем аннотация для людей, которые это потом будут читать (на x86)
Здравствуйте, Lazin, Вы писали:
L>то там в шаблоне переменной просто значение присвоится и все, прямо как у топикстартера в примере, ну то есть по сути, store(value, memory_order_relaxed) — не более чем аннотация для людей, которые это потом будут читать (на x86)
Атомарно же? А больше от relaxed, вроде бы, ничего и не требуется
Здравствуйте, Abyx, Вы писали:
A>Какие-то люди писали выше что достаточно написать volatile. Нет. Volatile не убирает race condition, код по прежнему будет приводить к UB.
Сколько тут было ответов и все почему-то подумали, что предлагалось использовать только volatile.
Между тем, предлагалось добавить volatile к решению с мьютексами (volatile bool + mutex).
fdn721, предложил именно добавить volatile в целях запретить оптимизации с bool флагом (именно об этом (об оптимизации) был вопрос), а не заменить на volatile всю синхронизацию.
L>то там в шаблоне переменной просто значение присвоится и все, прямо как у топикстартера в примере, ну то есть по сути, store(value, memory_order_relaxed) — не более чем аннотация для людей, которые это потом будут читать (на x86)
:doublefacepalm: прочитай уже [intro.multithread]. Там написано про и про UB и про relaxed.
Я вообще фигею. Люди не читали даже *вводную* часть стандарта, а сидят тут с умными щами и пытаются что-то доказывать.
Здравствуйте, wander, Вы писали:
W>Между тем, предлагалось добавить volatile к решению с мьютексами (volatile bool + mutex). W>fdn721, предложил именно добавить volatile в целях запретить оптимизации с bool флагом (именно об этом (об оптимизации) был вопрос), а не заменить на volatile всю синхронизацию.
volatile не добавляет коду с мьютексами ничего полезного.
A>:doublefacepalm: прочитай уже [intro.multithread]. Там написано про и про UB и про relaxed. A>Я вообще фигею. Люди не читали даже *вводную* часть стандарта, а сидят тут с умными щами и пытаются что-то доказывать.
Может уже хватит придираться? Я просто намекнул на то, что relaxed семантика это просто обычные загрузки и сохранения, если ты читал стандарт, то должен и сам это знать. Я лично, отношусь к этому как к аннотации для читателя кода, о чем и написал, ну и где здесь faceplam?
Где Вы здесь усмотрели race condition (результат выполнения зависит от порядка исполнения потоков)? (пока считаем, что никакой оптимизации нет над переменной)
T>Вопрос: что помешает компилятору и в этом случае оптимизировать чтение значения flag примерно вот так: T>
T>void thread_1_sync_real()
T>{
T> bool f;
T> scoped_lock lock(m) {
T> f = flag;
T> }
T> while (true) {
T> if (f) break;
T> work();
T> }
T>}
T>
Компилятор не будет так жонглировать кодом: из void thread_1_sync() он никак не сделает void thread_1_sync_real()...Разные функции абсолютно у Вас получились.