LINUX.ORG.RU

Гонка данных - тонкие тонкости?

 ,


1

3

Вот есть переменная типа uint64_t в которой лежит какая то чиселка а. Первый поток в нее пишет чиселку b, а второй поток ее этот момент читает. Вопрос - какие именно чиселки может получить второй поток при чтении? Только чиселки a, b или вообще что угодно?

★★★★★

Зависит от платформы и выравнивания. Может быть и гарантированно атомарная операция, а может и хрень полная.

anonymous
()

Дали им Rust, дали им атомарные операции - не хочу, хочу яйцами в пилораму.

anonymous
()
Ответ на: комментарий от AntonI

Допустим я ее выравняю на 64 бита.

Это платформозависимая информация, по определению.

Например, возьмем мануал Intel, том 3, глава 8.1.1:

Процессоры Pentium (а также более новые) гарантируют, что следующие операции с памятью всегда будут выполняться атомарно:

  • Чтение или запись 64-битного слова с 64-битным выравниванием.

goto-vlad
()
Ответ на: комментарий от anonymous

Спасибо кэп, я в курсе про atomic.

AntonI ★★★★★
() автор топика
Ответ на: комментарий от goto-vlad

Это платформозависимая информация, по определению.

По какому определению? По определению стандарта это UB.

anonymous
()
Ответ на: комментарий от anonymous

По какому определению? По определению стандарта это UB.

Автор спрашивает, как это реализовано на конкретном железе. Информация, которую полезно знать всем, кто понимает, что их программы выполняются не в абстрактной виртуальной машине.

goto-vlad
()

Без примитивов синхронизации возможен на современных компьютерах даже такой казус. Ты можешь раньше второго потока из первого записать b, а во втором потоке позже прочитать старое значение a. Во как!

dave ★★★★★
()
Ответ на: комментарий от AntonI

Вы точно правильно понимаете смысл выражения Undefined Behaviour? Вы можете прочитать вообще что угодно и откуда угодно. Компилятор вам ничего не гарантирует, в том числе, что когда он в следующий раз будет собирать тот же самый код вы получите тот же самый результат.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

с этим нынче сложно. нубы пишут код с UB, конфликтами доступа, а потом удивляются, что «раньше работало, а теперь перестало». и это ещё не говоря про возможные ограничения ресурсов, одноядерные процы и дедлоки, и многое другое.

Iron_Bug ★★★★★
()
Ответ на: комментарий от goto-vlad

Автор спрашивает, как это реализовано на конкретном железе.

У автора в тегах c++, а в посте uint64_t. Это не про конкретное железо.

anonymous
()
Ответ на: комментарий от goto-vlad

кто понимает, что их программы выполняются не в абстрактной виртуальной машине

Не понимает. Таки именно что на абстрактной - в llvm в явном виде, в gcc в неявном, но тоже. Поэтому с хрена ли вы взяли что в сгенерированном машинном окажется 64битная операция которую вы подразумеваете? В отсутствие барьеров компилятор, например, может не весь регистр записать, если ему так удобнее будет, или вместо записи использовать какие-то более экзотические операции типа inc/dec/or/and в зависимости от того что с данными делается в коде.

Поэтому вбейте уже себе в головы - когда UB, то всё, никаких больше рассуждений что там на «самом деле» быть не может. Это UB.

Только чиселки a, b или вообще что угодно?

Это UB!

slovazap ★★★★★
()
Ответ на: комментарий от goto-vlad

Процессоры Pentium (а также более новые) гарантируют, что следующие операции с памятью всегда будут выполняться атомарно: Чтение или запись 64-битного слова с 64-битным выравниванием.

Даже на 32-битном процессоре?

anonymous
()
Ответ на: комментарий от Iron_Bug

с этим нынче сложно. нубы пишут код с UB, конфликтами доступа, а потом удивляются, что «раньше работало, а теперь перестало». и это ещё не говоря про возможные ограничения ресурсов, одноядерные процы и дедлоки, и многое другое.

Разве не в этом вся суть C++? Напихать как можно больше undefined behaviour в стандарт языка, а потом сваливать всё на безруких нубов.

hateyoufeel ★★★★★
()
Ответ на: комментарий от anonymous

Даже на 32-битном процессоре?

Вот точная цитата из мануала

8.1.1

Guaranteed Atomic Operations

The Intel486 processor (and newer processors since) guarantees that the following basic memory operations will always be carried out atomically:

  • Reading or writing a byte
  • Reading or writing a word aligned on a 16-bit boundary
  • Reading or writing a doubleword aligned on a 32-bit boundary

The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically:

  • Reading or writing a quadword aligned on a 64-bit boundary
  • 16-bit accesses to uncached memory locations that fit within a 32-bit data bus

The P6 family processors (and newer processors since) guarantee that the following additional memory operation will always be carried out atomically:

  • Unaligned 16-, 32-, and 64-bit accesses to cached memory that fit within a cache line
Deleted
()
Ответ на: комментарий от slovazap

Таки именно что на абстрактной - в llvm в явном виде, в gcc в неявном, но тоже.

Даже если бы LLVM пользовались как виртуальной машиной, это всё равно была бы конкретная виртуальная машина. И она нифига не в терминах абстрактной ВМ, которую описывает стандарт. А так, их IR это промежуточное внутреннее представление, у GCC в качестве аналога используется GENERIC/GIMPLE.

xaizek ★★★★★
()
Ответ на: комментарий от Deleted

И вот примеры, как на 32-битном x86 атомарно манипулировать 64-битными данными https://stackoverflow.com/a/48046658

Кстати, у ARM (по крайней мере у многих) тоже есть похожие гарантии - некоторые инструкции даже без лока шины (т.е. «без atomic») пишут и читают атомарно данные с выравниванием

Deleted
()
Ответ на: комментарий от xaizek

конкретная виртуальная машина

Начнем с того что это несколько тысяч «конкретных виртуальных машин», по числу версий * архитуктур * наборов флагов оптимизации. Не, чисто в теории ты можешь взять конкретную версию компилятора, зашить архитуктуру и набор флагов, изучить её работу во всех случаях, и вот тогда UB станет вполне себе defined, и можно будет говорить о том что там в каком случае под капотом происходит. Но пока ты этого не сделал и наблюдаемое поведение будет разное даже в минимально различающихся окружениях, даже заикаться об этом - проявление глубочайшего дилетантства.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Я о UB не заикался, а сказал, что LLVM не является абстрактной виртуальной машиной C или C++.

xaizek ★★★★★
()

сделай отдельный io-поток ReaderWriter и обрабатывай последовательно запросы на запись/чтение в порядке поступления

anonymous
()
Ответ на: комментарий от slovazap

Это UB!

В классическом примере с data-race, где два потока инкрементируют один int наперебой, я че то ни разу не видел что бы получалось совершенно произвольное число - ни на практике, ни в книжках. В этом случае переменная увеличивается не более чем на суммарное число инкрементов. Это тоже UB, и UB бывает ну очень разное.

AntonI ★★★★★
() автор топика
Ответ на: комментарий от AntonI

О-о-о, да что ты? Ужель ты на практике испытал все версии всех компиляторов (включая все будущие) подо все архитектуры, чтобы такое утверждать? Или знаешь автора книги который это сделал? Или просто не осилил прочитать написанное мной?

slovazap ★★★★★
()
Ответ на: комментарий от AntonI

я че то ни разу не видел

Так это же чиселки. Тонкие тонкости.

anonymous
()
Ответ на: комментарий от Deleted

Я конечно не настоящий сварщик, но я так понимаю, это если ты программируешь на ассемблере, то эти гарантии под конкретную платформу у тебя будут. А если компилятором, то гарантий уже нет.

anonymous
()
Ответ на: комментарий от hateyoufeel

нет, это не сишечка виновата. это у тебя в голове не хватает чего-то, чтобы её осилить. причём подозрительно то, что я на Си программировала даже в детстве. и если у кого-то в голове меньше мозга, чем у меня было в 12 лет, то у меня возникают подозрения, что этот человек не может называться программистом.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от anonymous

А из этого вытекает, что другие процессоры не увидят полрегистра?

В случае 32-битных процессоров это актуально в первую очередь при использовании MMX-инструкций.

anonymous
()
Ответ на: комментарий от anonymous

Процессоры Pentium

Даже на 32-битном процессоре?

[цитата из интеловского мануала]

А из этого вытекает, что другие процессоры не увидят полрегистра?

Какие «другие» процессоры? У AMD тоже есть такие гарантии.

Или речь о соседних вычислительных ядрах? Если да, то вытекает. Глава так и называется «CHAPTER 8. MULTIPLE-PROCESSOR MANAGEMENT». Не ленитесь, почитайте, если действительно интересно.

Deleted
()

@AntonI, вот тебе две полезные ссылки (особенно первая), где люди обсуждают атомарность на x86 и стандарт C++. То есть, можно быть одновременно в курсе многих деталей твоей аппаратной платформы, и при этом писать код без UB.

Путь к просвещению – хотя бы иногда заглядывать в код, сгенерированный компилятором, разбираться как оно всё работает. Ты на правильном пути, а @slovazap пусть и дальше кричит «Вы всё врете!», я думаю он в прошлом был javascript-программистом, поэтому и привык писать код в стиле «Ну, мы люди маленькие, нам не положено заглядывать под капот».

https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86/36685056

https://stackoverflow.com/questions/38447226/atomicity-on-x86

anonymous
()
Ответ на: комментарий от anonymous

А если компилятором, то гарантий уже нет.

Да, конечно. Но такие штуки как правило используют из необходимости и понимая что к чему. Не обязательно писать весь код ассемблером, для гарантий достаточно небольшой вставки.

Судя по треду, многие не очень понимают зачем это нужно и выдумывают какую-то дичь про туповатых хацкеров (и умных себя). Напишу чуть подробнее.

Есть класс задач, где нужны wait-free (в бытовом смысле, которые вообще не ждут соседей) алгоритмы. Гарантии атомарной записи, гарантии порядка записи нужны как раз для такого. «Стандартные» atomic-переменные - это области памяти, которые для обновления (на x86/64) используют lock-префиксы для инструкций и mfence в качестве барьера. В некоторых случаях это получается неприемлемо медленно. Можно придумать более сложные, более производительные, и, главное, по-настоящему wait-free алгоритмы, но, скажем, с особенностями: вычислительное ядро будет видеть данные, которые обновило соседнее ядро с некоторым запозданием.

Deleted
()
Ответ на: комментарий от Deleted

Да, речь о соседних процессорах (или ядрах, кому как удобнее). Спасибо за информацию.

anonymous
()
Ответ на: комментарий от Iron_Bug

Как я тебя понимаю! Я в 12 мог стометровку пробежать за 9 секунд, жал 100кг от груди, а ещё у меня член был полметра. Не то что нынешняя молодёжь!

hateyoufeel ★★★★★
()
Ответ на: комментарий от anonymous

«Стандартные» atomic-переменные - это области памяти, которые для обновления (на x86/64) используют lock-префиксы для инструкций и mfence в качестве барьера.

Тоже напишу чуть подробнее. В комитете сидят не совсем умственно отсталые, и если на данной архитектуре данный тип данных будет атомарным без lock-префиксов, то в компиляторе это делают:

внезапно: https://en.cppreference.com/w/cpp/atomic/atomic_is_lock_free

+100 к этому анониму: Гонка данных - тонкие тонкости? (комментарий)

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

М?

All atomic types except for std::atomic_flag may be implemented using mutexes or other locking operations, rather than using the lock-free atomic CPU instructions

atomic CPU instructions - это и есть инструкции с lock-префиксами.

Lock-free в бытовом понимании и в формальном отличаются, если интересно - почитайте хоть википедию, что ли. (спойлер - инструкции с lock-префиксами считаются lock-free)

То есть я не настаиваю, конечно, можете считать как угодно, но если действительно интересно разобраться, то лучше, пожалуй, почитать.

Deleted
()
Ответ на: комментарий от Deleted

«Стандартные» atomic-переменные - это области памяти, которые для обновления (на x86/64) используют lock-префиксы для инструкций и mfence в качестве барьера.

Именно для этого в атормарных операциях есть понятие Ordering. Так запись в atomic_uint32_t с relaxed ordering в релизе на Intel сгенерирует код, идентичный обычному присвоению uint32_t (проверено). Однако, в отличие от прямой записи в uint32_t, здесь не будет undefined behaviour и компилятор будет четко понимать, что ты от него хочешь.

Нет вообще никакого смысла переходить с атомиков на обычные переменные. Я бы даже больше сказал - это противопоказано.

Marvel
()
Ответ на: комментарий от Marvel

Так запись в atomic_uint32_t с relaxed ordering в релизе на Intel сгенерирует код, идентичный обычному присвоению uint32_t (проверено).

У компилятора больше возможностей оптимизировать код с «обычными» переменными

https://godbolt.org/z/OCblo8

Нет вообще никакого смысла переходить с атомиков на обычные переменные. Я бы даже больше сказал - это противопоказано.

Как скажете

Deleted
()

Это определяется конкретным железом.

cvv ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.