Здравствуйте, vdimas, Вы писали:
V>·>т.к. такое его использование — data race, а значит ub. Использование std::atomic не является data race. V>1. Data race не всегда UB, часто так и задумано.
Всегда.
Не путай data race и race condition.
V>2. Использование std::atomic, защищает от data race неатомарные типы, для специализаций под эти типы структура std::atomic содержит внутри себя мьютекс. V>Что характерно, что для таких типов memory_order не играет никакого значения, т.к. оперирование примитивами синхронизации означает выполнение самых строгих гарантий, т.е. обсуждаемый memory_order актуален только для типов, которые проц умеет сохранять/читать атомарно.
Опять фантазируешь. Мьютекс содержат только те std::atomic, которые данная имплементация языка не может защитить от data race без мьютекса. И это можно даже проверить для каждого конкретного случая, см. метод is_lock_free().
V>·>Верно. Но это обработчик сигнала будет вызван снаружи. V>Не важно.
Важно. Т.к. в этом случае корректность кода обеспечивается языком и операционкой. Прямой вызов хендлера в описанной тобой ситуации — ошибка программиста, баг в программе.
V>·>А тред должен дёргать signal(). V>Вызов signal устанавливает обработчика, а не генерирует сигнал. V>Сигнал посылает kill/raise/alarm.
Да, точно. Опечатка.
V>Сигнал посылается процессу, а не потоку.
Именно!
V>·>В этом случае сработают соответсвующие механизмы atomic. V>А как до атомик-то жили? ))
Я ж отвечал уже — платформено-зависимыми функциями — ассемблерные вставки, сисколлы.
V>Т.е. обработчики сигнала могут работать конкурентно
Не могут. Опять фантазии, явно противоречащие спекам:
Signals generated for a process are delivered to only one thread. Thus, if more than one thread is eligible to receive a signal, one has to be chosen.
Здравствуйте, alex_public, Вы писали:
_>Ты не совсем правильно понимаешь. Применение volatile ортогонально и многозадачности и memory-mapped и вообще чему-либо "реальному". Это исключительно внутреннее дело компилятора C/C++, которому разрешают или не разрешают определённые вольности при генерации кода. Применяться это может в очень разных местах.
Я-то как раз это понимаю. Проблема как раз в том, что volatile имеет слишком невнятную семантику, чтобы иметь практическую пользу.
Если мы отвлечёмся от задачи "управления компилятором" и подумаем о реальных сценариях, то внезапно наступает озарение: для практических сценариев volatile как модификатор типа одновременно слишком груб и недостаточно силён.
Как выражался по этому поводу тов. Торвальдс, "Data isn't volatile — accesses are volatile".
Исключением являются сценарии memory-mapped hw. И то, ровно потому, что по адрес, отображённый на железку, волатильны все обращения.
_>Всё, что ты тут написал может быть справедливо например для написания ОС, но скажем для написания драйвера мотора на конкретном МК, это уже не актуально. )))
Не верю. Даже для конкретного МК с конкретным мотором, вы полагаетесь на свою способность угадать, куда компилятор разместит переменную i. Либо я чего-то не понимаю в спецификации C, либо вы пишете код, который завтра сломается на том же МК — просто выше по коду будут сделаны изменения, которые приведут к смене места хранения для i.
Что ещё хуже — вы пишете платформменно-зависимый код на платформенно-независимом языке. Кто помешает кому-то (например вам же через три года) скомпилировать этот же код для другого МК?
Компилятор вам ничего не подскажет; просто код будет крутить мотор неправильно.
_>Смотри, спинлок — это по сути почти такой же код, как в моём первом примере на тему volatile, только он после изменения ячейки памяти (и окончания блокировки) должен мгновенно вернуть старое значение в ячейку — осуществить захват ресурса. Точнее слово "мгновенно" тут даже не верно: проверка флага и его установка (в случае нулевого значения) должна происходить атомарно! И соответственно для возможности реализации такого кода, нужна соответствующая поддержка от процессора. Обычно это реализуется через атомарную инструкцию CAS (CMPXCHG для х86, доступна в C++ через эту https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange функцию класса atomic), которая как раз создана для таких целей и является базовым кирпичиком практически всех lock-free алгоритмов.
_>Казалось бы при применение CAS никакие volatile не нужны, достаточно только наличие у железа подобной инструкции. Но есть нюансы! ))) Инструкция CAS, как и все атомарные инструкции типа "прочитать-изменить-записать", весьма медленная. Поэтому частенько делают более интеллектуальную схему реализации спинлока: вставляют дополнительный блокирующий цикл ожидания, на обычном быстром чтение памяти (в точности как мой первый пример), а уже после его "прохода" реализуют захват на медленной атомарной функции. Ну и соответственно для возможности написания такой оптимизации, тебе очевидно потребуется volatile.
А можно посмотреть на реальный пример такой реализации спинлока?
Все, которые я видел, крутят в цикле именно CAS, считая её достаточно быстрой. _>Вообще довольно иронично тут получается — для возможности оптимизации, тебе необходима возможность отмены другой оптимизации. )))
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали: S>Всё верно. Я ни разу и не утверждал, что volatile появился после принятия стандарта. Но вам стоило бы задуматься о том, что первое издание K&R вышло в 1978, и достаточно полно отражало язык в тот момент. S>Задача написания ядра Unix к тому моменту уже несколько лет как была завершена.
Откуда ты это берешь?
См. даты релизов System III, System IV, System V.
По последней см. даты rev 2, rev 3, rev 4.
Разработка активно велась до 88-89-го.
Исходники доступны, если влом качать, можно оценить, например, их зазипованный объем: от ~2 MB первых версий до ~15 MB последних.
V>>Тогда в основном были мини-ЭВМ или мейнфреймы, эта лицензия, считай, на десятки-сотни человек. S>Ну да.
До просто в мире нейтива уже в 80-90-х годах по 500-600 баксов за одну лицензию какой-нить GUI-либы как здрасьте.
Поэтому, нифига это не дорого.
А тут 80-е сравниваем с 70-ми.
А смысл, если в 80-е уже пошли персоналки вовсю?
ИМХО, ты даже не можешь определиться/сформулировать, что и кому ты хочешь доказать. ))
Здравствуйте, vdimas, Вы писали:
V>А тут 80-е сравниваем с 70-ми. V>А смысл, если в 80-е уже пошли персоналки вовсю? V>ИМХО, ты даже не можешь определиться/сформулировать, что и кому ты хочешь доказать. ))
Что разработка Unix прекрасно себя чувствовала без volatile ажно с 1967 по 1985. Несмотря на многозадачность и прочие умные слова.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, ·, Вы писали:
V>>·>т.к. такое его использование — data race, а значит ub. Использование std::atomic не является data race. V>>1. Data race не всегда UB, часто так и задумано. ·>Всегда. ·>Не путай data race и race condition.
Давай разбираться, кто путает:
The definition of a data race is pretty clear, and therefore, its discovery can be automated. A data race occurs when 2 instructions from different threads access the same memory location, at least one of these accesses is a write and there is no synchronization that is mandating any particular order among these accesses.
A race condition is a semantic error. It is a flaw that occurs in the timing or the ordering of events that leads to erroneous program behavior.
V>>2. Использование std::atomic, защищает от data race неатомарные типы, для специализаций под эти типы структура std::atomic содержит внутри себя мьютекс. V>>Что характерно, что для таких типов memory_order не играет никакого значения, т.к. оперирование примитивами синхронизации означает выполнение самых строгих гарантий, т.е. обсуждаемый memory_order актуален только для типов, которые проц умеет сохранять/читать атомарно. ·>Опять фантазируешь.
Тут лучше не выдумывать из головы, а внимать.
Ценной информацией делюсь, ваще-то.
Например, насчёт выделенного.
·>Мьютекс содержат только те std::atomic, которые данная имплементация языка не может защитить от data race без мьютекса.
Data race в случае memory_order_release происходит в полный рост для атомарных типов.
При некоторых других комбинациях (на стороне записи и чтения) — тоже аж бегом.
·>И это можно даже проверить для каждого конкретного случая, см. метод is_lock_free().
Проверить-то можно, но не то, что ты формулируешь словами.
Продолжаешь не понимать предмет.
V>>·>Верно. Но это обработчик сигнала будет вызван снаружи. V>>Не важно. ·>Важно. Т.к. в этом случае корректность кода обеспечивается языком и операционкой. Прямой вызов хендлера в описанной тобой ситуации — ошибка программиста, баг в программе.
Интересная заява. ))
И как ты собрался её подтверждать, мне уже любопытно?
V>>·>В этом случае сработают соответсвующие механизмы atomic. V>>А как до атомик-то жили? )) ·>Я ж отвечал уже — платформено-зависимыми функциями — ассемблерные вставки, сисколлы.
Для однопроцессорных мультизадачных операционок никаких дополнительных приседаний в виде ассемблерных вставок не требовалось.
И никакой atomic для семантики memory_order_release не требуется даже для мультипроцессорных.
Требуется атомарность.
Впрочем, ты уже показал тут десятки раз, что не понимаешь, что такое атомарность.
V>>Т.е. обработчики сигнала могут работать конкурентно ·>Не могут. Опять фантазии, явно противоречащие спекам: ·>Signals generated for a process are delivered to only one thread. Thus, if more than one thread is eligible to receive a signal, one has to be chosen. ·>(c) http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html#tag_22_02_04_02
А смысл цитировать некие частности после моего:
В зависимости от реализации POSIX, даже в зависимости от версий Linux, по приходу сигнала поведение было и есть разное.
Некоторые сигналы вызываются первым попашимся потоком, который не замаскировал этот сигнал.
Некоторые сигналы вызывались у всех потоков, порождённых данным процессом.
Некоторые сигналы (напр. переполнение с плавающей точкой) вызываются у конкретного потока, в котором произошла ошибка.
Сверху еще посылка сигналов конкретным потокам через либу pthread.
В общем, не надо пытаться всё упрощать до удобной тебе модельки, реальность чуть сложнее.
·>Т.е. операционка следит за тем, чтобы сигналы процессу шли последовательно.
И опять не понимаю, зачем ты насасываешь из пальца столь громкие утверждения?
— сигналы могуть быть реентерабельны;
— сигналы могут возникать одновременно/независимо.
Например, в одном потоке AV, в другом переполнение математики.
А у тебя в программе один обработчик на все сигналы (часто так и делают).
Здравствуйте, ·, Вы писали:
_>>·>И на каком основании ты так считаешь? _>>На том основание, что на всем железе инструкция записи регистра в память атомарна, ·>Это закон природы?
Хороший тон проектирования.
Для опровержения достаточно показать железо, где это не так.
_>>а присвоение переменной типа int скомпилируется именно в такую инструкцию. ·>А это на каком основании?
Ширина int зависит от платформы.
Например, может быть 18 бит.
·>Я не о конкретно данной ситуации. Меня повеселил факт, что оптимизации в принципе можно считать опасными.
Здравствуйте, ·, Вы писали:
_>>На том основание, что на всем железе инструкция записи регистра в память атомарна, ·>Это закон природы?
Скорее закон целесообразности. )
_>>а присвоение переменной типа int скомпилируется именно в такую инструкцию. ·>А это на каком основании?
На том основание, что int умещается в один регистр. )
_>>Там нет кода, который изменил бы своё поведение, в случае "кэширования" значения переменной в регистре. ·>Это по коду не видно. Там вызывается некий метод process который может тоже делать хз что, а компилятор может видеть и этот код и принимать свои решения.
Не, как раз всё видно. Вот если бы там было например по две операции чтения/записи нашей переменной в каждом потоке, то уже можно было начинать беспокоиться... )
_>>Ты похоже как-то плохо умеешь читать текст на русском языке. Я же однозначно написал, что как раз в моём примере такого кода нет. А вот если бы был, то тогда и модификатор volatile появился бы. ·>Я не о конкретно данной ситуации. Меня повеселил факт, что оптимизации в принципе можно считать опасными.
Ну я вроде как рядом приводил вполне конкретные примеры, в которых это вполне актуально. _>>Перестановку обращений к одной области памяти тупо недопустит сам компилятор, т.к. это изменит поведение кода. ))) ·>Компилятор, если ему не сказано явно, не учитывает поведение других потоков, поэтому он может допускать всё что угодно, что не противоречит наблюдаемым эффектам однопоточного исполнения данной части кода.
Правильно. И как ты видишь перестановку обращений к одной области памяти, которая не поменяет поведение однопоточного кода? ) Ну и которая не сводится к объединению двух одинаковых команд в одну (что отключается через volatile).
_>> Если конечно речь не идёт об оптимизациях типа объединения двух последовательных записей — это возможная оптимизация и если она вдруг тебе мешает, то ты можешь отключить её с помощью volatile. ·>Ох.. Повторяюсь. Записи оптимизировать может не только компилятор, но и процессор. А процессор про volatile ничего не знает.
Ты всё время путаешь два типа оптимизаций. В одной возможны перестановки команд, а в другой возможны слияния или даже опускания команд. Первый тип реализуется и компилятор и процессором. И отключается через соответствующие fence вызовы. А второй типа реализуется только компилятором и отключается для переменных с модификатором volatile.
Здравствуйте, ·, Вы писали:
_>>Для полноценного спинлока ·>Прям классика! Настоящий шотландец. _>> тебе не хватит ни volatile, ни fences. Для него необходимы уже атомарные операции типа CAS или аналоги, поддерживаемые процессором. ·>вот такой себе самый обычный код: ·>
·>volatile sig_atomic x = 0;//или даже boolean
·>...
·>//thread1:
·>x = 1;
·>//thread2:
·>while(!x) {}
·>
·>никаких CAS не надо, а вот fence нужен и volatile не поможет.
Ты бы хоть немного разобрался в теме, прежде чем болтать и загонять мнение о тебе совсем в минус. )
Спинлок захватывает ресурс (как и мьютекс). А это у тебя код убогой реализации "условной переменной" или же события (в другой терминологии).
Здравствуйте, Sinclair, Вы писали:
V>>А смысл, если в 80-е уже пошли персоналки вовсю? V>>ИМХО, ты даже не можешь определиться/сформулировать, что и кому ты хочешь доказать. )) S>Что разработка Unix прекрасно себя чувствовала без volatile ажно с 1967 по 1985. Несмотря на многозадачность и прочие умные слова.
Ну, продолжай, продолжай.
А дальше эта разработка должна была остановиться?
Или в исходном утверждении что-то говорилось о конкретных годах?
Мои рассуждения тут просты:
Почти 100% пользовательских программ в те годы оставались однопоточными;
Собсно, UNIX-way — он именно об этом, когда в прикладных процессах у нас ровно один поток;
Само понятие "поток" в UNIX появилось относительно поздно — надо найти дату выхода POSIX 1.C.
До появления потоков был простой fork, получались два независимых процесса, опять здравствуй однопоточность в каждом из них.
Наиболее востребованной конструкция volatile была (и есть) в системном ПО, т.е. запрос на эту конструкцию, одновременно с появлением оптимизирующих компиляторов, идёт из системной части (дрова, ядро, управление виртаульной памятью, в т.ч. в случае маппинга портов аппаратуры на участки такой памяти).
Ссылки на исходники ядра Linux я дал именно с этой целью — показать, насколько широко эта конструкция используется именно там.
В прикладном коде эта конструкция почти никогда не нужна и даже опасна — разработчик должен хорошо понимать происходящее (увы, та самая "планка входа"), поэтому, на прикладном уровне рулят семафоры, мьютексы, HEVENT, conditional variables и т.д. и т.п.
И да, на всякий случай, семантика volatile в С/С++ отличается от семантики volatile в C#.
Здравствуйте, vdimas, Вы писали:
V>>>·>т.к. такое его использование — data race, а значит ub. Использование std::atomic не является data race. V>>>1. Data race не всегда UB, часто так и задумано. V>·>Всегда. V>·>Не путай data race и race condition. V>Давай разбираться, кто путает:
В этом разбираться не надо. Надо разобраться что ты напутал.
If a data race occurs, the behavior of the program is undefined.
А вот race condition действительно относится к семантике и может быть "так и задумано".
V>>>2. Использование std::atomic, защищает от data race неатомарные типы, для специализаций под эти типы структура std::atomic содержит внутри себя мьютекс. V>>>Что характерно, что для таких типов memory_order не играет никакого значения, т.к. оперирование примитивами синхронизации означает выполнение самых строгих гарантий, т.е. обсуждаемый memory_order актуален только для типов, которые проц умеет сохранять/читать атомарно. V>·>Опять фантазируешь. V>Тут лучше не выдумывать из головы, а внимать. V>Ценной информацией делюсь, ваще-то. V>Например, насчёт выделенного.
Об этом я тут рассказывал где-то неделю назад.
V>·>Мьютекс содержат только те std::atomic, которые данная имплементация языка не может защитить от data race без мьютекса. V>Data race в случае memory_order_release происходит в полный рост для атомарных типов. V>При некоторых других комбинациях (на стороне записи и чтения) — тоже аж бегом.
Нет. std::atomic гарантирует отсутсвие data race:
"A program that has two conflicting evaluations has a data race unless ... both conflicting evaluations are atomic operations (see std::atomic) ..."
Без всяких оговорок о конкретном memory_order или о том, какими типами они специализированы.
V>·>И это можно даже проверить для каждого конкретного случая, см. метод is_lock_free(). V>Проверить-то можно, но не то, что ты формулируешь словами.
Я цитирую стандарт.
V>·>Важно. Т.к. в этом случае корректность кода обеспечивается языком и операционкой. Прямой вызов хендлера в описанной тобой ситуации — ошибка программиста, баг в программе. V>Интересная заява. )) V>И как ты собрался её подтверждать, мне уже любопытно?
По-моему это настолько очевидно, что я даже не понимаю что здесь требуется подтверждать. Тот факт, что мы передали указатель на функцию куда-то, это ещё не означает, что эту функцию стало безопасно дёргать из многопоточки.
V>>>·>В этом случае сработают соответсвующие механизмы atomic. V>>>А как до атомик-то жили? )) V>·>Я ж отвечал уже — платформено-зависимыми функциями — ассемблерные вставки, сисколлы. V>Для однопроцессорных мультизадачных операционок никаких дополнительных приседаний в виде ассемблерных вставок не требовалось.
В смысле "у меня всё работало"? Отличный аргумент.
V>>>Т.е. обработчики сигнала могут работать конкурентно V>·>Не могут. Опять фантазии, явно противоречащие спекам: V>·>Signals generated for a process are delivered to only one thread. Thus, if more than one thread is eligible to receive a signal, one has to be chosen. V>·>(c) http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html#tag_22_02_04_02 V>А смысл цитировать некие частности после моего: V>
V>В зависимости от реализации POSIX, даже в зависимости от версий Linux, по приходу сигнала поведение было и есть разное.
V>Некоторые сигналы вызываются первым попашимся потоком, который не замаскировал этот сигнал.
V>Некоторые сигналы вызывались у всех потоков, порождённых данным процессом.
V>Некоторые сигналы (напр. переполнение с плавающей точкой) вызываются у конкретного потока, в котором произошла ошибка.
V>Сверху еще посылка сигналов конкретным потокам через либу pthread.
И что? Ничто из этого не означает, что обработчик сигнала можно вызывать из треда напрямую. Ядро ответственно за корректную синхронизацию вызовов хендлеров.
V>·>Т.е. операционка следит за тем, чтобы сигналы процессу шли последовательно. V>И опять не понимаю, зачем ты насасываешь из пальца столь громкие утверждения?
_Посылаться_ они могут конкруентно, да. А ты утверждаешь, что обработчик сигнала может дёргаться конкурентно. Сигналы вызываются асинхронно, а не конкурентно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, vdimas, Вы писали:
_>>>·>И на каком основании ты так считаешь? _>>>На том основание, что на всем железе инструкция записи регистра в память атомарна, V>·>Это закон природы? V>Хороший тон проектирования. V>Для опровержения достаточно показать железо, где это не так.
Ага, стронгли белив.
_>>>а присвоение переменной типа int скомпилируется именно в такую инструкцию. V>·>А это на каком основании? V>Ширина int зависит от платформы. V>Например, может быть 18 бит.
И чё? У тебя с логикой всё в порядке?
"Ширина int зависит от платформы" следовательно "присвоение переменной типа int скомпилируется именно в такую инструкцию".
V>·>Я не о конкретно данной ситуации. Меня повеселил факт, что оптимизации в принципе можно считать опасными. V>Memory mapping тебе что-нибудь говорит?
Да. И?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Ikemefula, Вы писали:
_>>Кем и где делалось такое дикое утверждение? ) Я в любом случае подобного никогда не утверждал. И что-то сомневаюсь, что бы vdimas такое говорил, потому что, как это модно теперь здесь говорить, это был бы слишком большой залёт. ))) I>"Понятие многопоточности в языке появилось давно, одновременно с ключевым словом volatile." @vdimas
Я там за дискуссией не следил. Но конкретно из этой цитаты тот дикий тезис совершенно не следует. Потому как одновременность не означает причинность.
I>Этот же vdimas утверждает, что многопоточность появилась в языке посредством внешних средств. Ага, внутри дома, методом наружного присутствия, объявился некто.
Многопоточность появилась в стандарте начиная с C++11. До этого она реализовывалась через вызовы ОС API (что является в общем то нормой для системного языка), частенько обёрнутые в удобные сторонние библиотеки.
Правда мне не очень понятно о какой многопоточности говорит народ, делая при этом экскурсы в далёкое прошлое. В то время как современная многопочность появилась и в POSIX (pthread) и в Винде (win32 api) где-то в середине 90-ых. А до этого ничего подобного не было.
И да, конечно же UNIX всегда был многозадачной системой, но реализовывалось это через процессы (собственно всё делала одна функция fork), обменивающиеся между собой асинхронными сигналами. Кажется в 80-ых появилась разделяемая память и семафоры. И хотя это всё конечно же тоже было доступно в C/C++ через системное АПИ, однако это точно не было тем, что сейчас называют многопоточностью.
Здравствуйте, Sinclair, Вы писали:
_>>Ты не совсем правильно понимаешь. Применение volatile ортогонально и многозадачности и memory-mapped и вообще чему-либо "реальному". Это исключительно внутреннее дело компилятора C/C++, которому разрешают или не разрешают определённые вольности при генерации кода. Применяться это может в очень разных местах. S>Я-то как раз это понимаю. Проблема как раз в том, что volatile имеет слишком невнятную семантику, чтобы иметь практическую пользу. S>Если мы отвлечёмся от задачи "управления компилятором" и подумаем о реальных сценариях, то внезапно наступает озарение: для практических сценариев volatile как модификатор типа одновременно слишком груб и недостаточно силён. S>Как выражался по этому поводу тов. Торвальдс, "Data isn't volatile — accesses are volatile". S>Исключением являются сценарии memory-mapped hw. И то, ровно потому, что по адрес, отображённый на железку, волатильны все обращения.
Нуу спорный вопрос. Я так понимаю, что ты говоришь о чём-то типа введения блока кода volatile {}, вместо модификатора типа. С одной стороны конечно же может быть удобно управлять доступом руками, а с другой ведь можно и забыть его где-то проставить. А так компилятор сам проследит за каждым обращением к такой переменной. Например в *.h файлах для ядер ARM можно увидеть определение таких https://github.com/ARM-software/CMSIS/blob/master/CMSIS/Include/core_cm0.h#L199 модификаторов, через которые потом определяются все структуры, регистры и т.п... )
_>>Всё, что ты тут написал может быть справедливо например для написания ОС, но скажем для написания драйвера мотора на конкретном МК, это уже не актуально. ))) S>Не верю. Даже для конкретного МК с конкретным мотором, вы полагаетесь на свою способность угадать, куда компилятор разместит переменную i. Либо я чего-то не понимаю в спецификации C, либо вы пишете код, который завтра сломается на том же МК — просто выше по коду будут сделаны изменения, которые приведут к смене места хранения для i.
Вот как раз с volatile там будет всё однозначно в этом смысле. )))
S>Что ещё хуже — вы пишете платформменно-зависимый код на платформенно-независимом языке. Кто помешает кому-то (например вам же через три года) скомпилировать этот же код для другого МК? S>Компилятор вам ничего не подскажет; просто код будет крутить мотор неправильно.
И тем не менее, я такой код видел не раз. Правда не у себя, а в различных чужих исходниках. )
_>>Смотри, спинлок — это по сути почти такой же код, как в моём первом примере на тему volatile, только он после изменения ячейки памяти (и окончания блокировки) должен мгновенно вернуть старое значение в ячейку — осуществить захват ресурса. Точнее слово "мгновенно" тут даже не верно: проверка флага и его установка (в случае нулевого значения) должна происходить атомарно! И соответственно для возможности реализации такого кода, нужна соответствующая поддержка от процессора. Обычно это реализуется через атомарную инструкцию CAS (CMPXCHG для х86, доступна в C++ через эту https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange функцию класса atomic), которая как раз создана для таких целей и является базовым кирпичиком практически всех lock-free алгоритмов. _>>Казалось бы при применение CAS никакие volatile не нужны, достаточно только наличие у железа подобной инструкции. Но есть нюансы! ))) Инструкция CAS, как и все атомарные инструкции типа "прочитать-изменить-записать", весьма медленная. Поэтому частенько делают более интеллектуальную схему реализации спинлока: вставляют дополнительный блокирующий цикл ожидания, на обычном быстром чтение памяти (в точности как мой первый пример), а уже после его "прохода" реализуют захват на медленной атомарной функции. Ну и соответственно для возможности написания такой оптимизации, тебе очевидно потребуется volatile. S>А можно посмотреть на реальный пример такой реализации спинлока? S>Все, которые я видел, крутят в цикле именно CAS, считая её достаточно быстрой.
Здравствуйте, ·, Вы писали:
_>>Это конечно верно, однако все остальные известные проблемы там хорошо развёрнуты, а вот про многопоточность даже упоминания нет... ))) Очень характерно для чего в реальности польза этого языка. ·>Т.е. пользу языка ты оцениваешь по тому, что в ~двух конкретных параграфах документации не описано то что ты хочешь. УмнО!
Я этот язык внимательно рассматриваю с таких давних пор, в которые ты даже мельком не слышал это название. ))) Причём рассматриваю не просто из абстрактного интереса, а с точки зрения замены им моего основного инструмента! Так что пользу его я отлично знаю. Правда чем дальше идёт время от 2014-го года, тем меньше я вижу смысла в переходе (сравнивая направление и темпы развития Rust и C++), но это отдельный вопрос.
_>>·>Почему ты решил, что она будет? _>>В соседнем сообщение же прямой ответ был. ) ·>Ты ссылку давай. Тут столько соседних сообщений, что я не знаю куда смотреть.
и ты на него уже даже давно отвечал. )))
_>>·>На самом деле вообще-то мы о сабже. А следовательно рассуждаем о безопасности, корректности и т.п кода. _>>·>Мой тезис был в том, что "volatile sig_atomic_t" нельзя использовать из многопоточного кода, т.к. программа получится с undefined behaviour. _>>Использовать можно. Хотя по факту скорее всего никогда не нужно (не припомню платформ, на которых была бы доступна классическая многопоточность, но при этом не было бы atomic), но это не отменяет нормальности работы этого кода (никаких data race). ·>Я несколько раз уже цитировал, что это data race по стандарту. Так что подтверждай высказывание стандартом, либо признавайся, что не прав.
Да, и в твоей цитате было ясно написано, что для атомарных данных data race не будет. )
_>>Ну вот найди мне определение атомарности в стандарте и будем говорить тогда о других определениях. ·>Objects of atomic types are the only objects that are free from data races, that is, they may be modified by two threads concurrently or modified by one and read by another.
Здравствуйте, alex_public, Вы писали:
_>в современных МК все нужные регистры всей периферии отображаются в адресное пространство процессора! Причём в совсем другую его область, чем отображена оперативная память.
Почему в другую область?
Например, в PIC-ах первые N байт внутренней оперативки — регистры управление портами и сами порты.
Потом идёт встроенная RAM, потом пустое адресное пространство.
Если PIC "многоножечный" с контроллером внешней памяти, то можно навесить еще и внешнюю оперативку на пустое адресное пространство.
_>Так что когда процессор видит инструкцию типа записи из регистра в ячейку памяти с подобным адресом, он на самом деле осуществляет запись в соответствующий регистр периферийного устройства. И это и есть собственно штатный способ взаимодействия с периферией процессора.
Здравствуйте, alex_public, Вы писали:
_>Применение volatile ортогонально и многозадачности и memory-mapped и вообще чему-либо "реальному".
Не совсем ортогональна.
Изначально многозадачность работала поверх одного процессора, просто через прерывания.
В персоналках потом тоже, если не брать кооперативную многозадачность виндов (были мультизадачные гипервизоры и под IBM PC).
В условиях такой многозадачности поверх одного проца не нужны ни спин-локи, ни CAS. Примитивы синхронизации работают через запрещение прерывания, что-то проверили-установили, разрешение прерывания.
Оптимизация в компиляторе в условиях работы по прерываниям может поломать семантику, volatile вернёт всё на место.
Здравствуйте, Иван Дубров, Вы писали:
ИД>Товарищ WolfHound прав насчёт когнитивной разгрузки -- Rust, например, позволяет взять малоопытных разработчиков и посадить писать их бизнес логику. И при этом не беспокоиться что они забудут там какой-то нибудь synchronized написать или данные замодифицируют так, что ты получишь состояние гонки в какой-нибудь совершенно параллельной части твоей системы.
Наличие synchronized в бизнес логике говорит о том, что вся система спроектирована криво и смена языка на rust не поможет. Нужно менять ДНК.
Здравствуйте, ·, Вы писали:
·>А вот race condition действительно относится к семантике и может быть "так и задумано".
Ты уже с терминологией решил начать спорить. ))
Race condition означает условное поведение всей программы в зависимости от как повезёт.
Data race — означает одновременные попытки читать и писать некие данные, или одновременные попытки писать.
Соотносятся эти определения следующим образом: data race может быть причиной race condition.
Но может быть и частью алгоритма.
·>Нет. std::atomic гарантирует отсутсвие data race:
Только для тех типов, для которых он специализируется с мьютексом унутре.
·>"A program that has two conflicting evaluations has a data race unless ... both conflicting evaluations are atomic operations (see std::atomic) ..." ·>Без всяких оговорок о конкретном memory_order или о том, какими типами они специализированы.
На заборе тоже написано... А компиллируешь — там дрова, обычный data race.
V>>·>И это можно даже проверить для каждого конкретного случая, см. метод is_lock_free(). V>>Проверить-то можно, но не то, что ты формулируешь словами. ·>Я цитирую стандарт.
Ты там делал странные выводы из просмотра исходников, а не цитировал стандарт.
Выводы неверные, достаточно посмотреть на реализацию atomic.
V>>·>Важно. Т.к. в этом случае корректность кода обеспечивается языком и операционкой. Прямой вызов хендлера в описанной тобой ситуации — ошибка программиста, баг в программе. V>>Интересная заява. )) V>>И как ты собрался её подтверждать, мне уже любопытно? ·>По-моему это настолько очевидно,
Нет, не очевидно.
·>что я даже не понимаю что здесь требуется подтверждать.
Свою громкую заяву требуется подтверждать, ес-но.
Особенно с учётом большого кол-ва инфы, которой тебя кормили несколько дней.
·>Тот факт, что мы передали указатель на функцию куда-то, это ещё не означает, что эту функцию стало безопасно дёргать из многопоточки.
А что в теле этой ф-ии рекомендуется лишь установить значение единственной переменной static volatile sig_atomic_t и выйти, ты уже забыл?
V>>·>Я ж отвечал уже — платформено-зависимыми функциями — ассемблерные вставки, сисколлы. V>>Для однопроцессорных мультизадачных операционок никаких дополнительных приседаний в виде ассемблерных вставок не требовалось. ·>В смысле "у меня всё работало"? Отличный аргумент.
У всех работало.
Исходники System V доступны, там для обсуждаемых вещей никаких ассемблерных вставок.
И да, у меня тоже работало. ))
На 3-м курсе по "Операционкам" была коллективная курсовая работа.
Для реального режима X86 писали многозадачную учебную операционку.
Мне с другом достался как раз планировщик и семафоры.
Садились на прерывание таймера, перепрограммировали ему частоту и вперёд, всё работает.
·>И что? Ничто из этого не означает, что обработчик сигнала можно вызывать из треда напрямую.
Это означает для начала, что твои частности не являлись аргументом.
А во вторых, разрешено всё, что не запрещено.
Вызывать собственный обработчик никем не запрещено.
·>Ядро ответственно за корректную синхронизацию вызовов хендлеров.
Нет там никакой корректной синхронизации и взяться неоткуда, бо это асинхронный, по отношению к происходящему в потоках, механизм.
Например, ядро линухов просто перебирает потоки, ищет первый попавшийся, который не замаскировался от сигнала, и асинхронно отправляет потоку прерывание, т.е. не ждёт возврата.
Пришёл следующий сигнал — опять перебирает потоки, находит подходящий, опять отправляет сигнал.
Т.е., возможны ситуации:
— два разных потока обрабатывают два разных сигнала одновременно;
— один и тот же поток обрабатывает оба сигнала, причём, второй сигнал залетел реентерабельно, не дожидаясь завершения работы первого обработчика.
Здравствуйте, ·, Вы писали:
V>>Для опровержения достаточно показать железо, где это не так. ·>Ага, стронгли белив.
Не, это у тебя вера в стандарт, а надо жить в реальном мире.
И там возможны варианты:
— компилятор не реализует что-то из стандарта;
— компилятор реализует что-то сверху стандарта.
— в стандарте местами поведение прямо отдаётся на откуп реализации и об этом делаются заметки, одну из которых я тебе уже цитировал.
_>>>>а присвоение переменной типа int скомпилируется именно в такую инструкцию. V>>·>А это на каком основании? V>>Ширина int зависит от платформы. ·>И чё? У тебя с логикой всё в порядке?
У меня-то да.
·>"Ширина int зависит от платформы" следовательно "присвоение переменной типа int скомпилируется именно в такую инструкцию".
А вот ты окружающих за идиотов держишь, походу. ))
Включил бы моск — а нафига вообще int привязан к ширине слова?
V>>·>Я не о конкретно данной ситуации. Меня повеселил факт, что оптимизации в принципе можно считать опасными. V>>Memory mapping тебе что-нибудь говорит? ·>Да. И?