Должен ли я блокировать переменную в одном потоке, если мне нужно только ее значение в других потоках, и почему это работает, если я этого не делаю?

Мне известно о этот вопрос, но я считаю, что мои опасения совсем другие.

Недавно я создал приложение SDL, используя многопоточность и OpenGL. У меня есть один поток, работающий в цикле, который постоянно обновляет состояние объектов, которые я рисую на экране. Состояния очень простые, это просто логический массив (когда значение массива истинно, я его рисую, когда ложно — нет).

В настоящее время у меня нет блокировки мьютекса ни для одной из моих переменных, и все работает нормально. Даже если только половина массива состояния была обновлена ​​между отрисовками, частота кадров намного выше (или, по крайней мере, равна) частоте обновления, поэтому было бы приемлемо иметь полуобновленное состояние.

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

  • Это случайно или есть причина, по которой не происходит нарушений доступа к памяти?
  • Если допустимо изменение состояния переменной непосредственно перед, во время или сразу после использования значения, должен ли я использовать блокировку мьютекса?

Спасибо за помощь.


Редактировать: дополнительная информация - массив создается динамически, но когда он создается/удаляется, я использую мьютекс (я полагал, что доступ к удаленной памяти не будет выглядеть любезно: P).


person Breakthrough    schedule 04.08.2011    source источник
comment
если массив не изменяется (например, размер, положение элементов), исключений не будет.   -  person Dasith Wijes    schedule 04.08.2011
comment
@Dasiths да, в связи с этим массив является статическим (позиция / размер постоянны, когда потоки работают).   -  person Breakthrough    schedule 04.08.2011


Ответы (2)


Теоретически совершенно недопустимо (неопределенное поведение) такой доступ к памяти без какой-либо синхронизации.

На практике это умеренно безопасно, если:

  1. Только один поток пишет, а все остальные читают.
  2. Вам все равно, если читатели не увидят некоторые из внесенных изменений некоторое время спустя (возможно, намного позже, чем фактическое время их написания).
  3. Вам все равно, увидят ли читатели изменения не по порядку, то есть они увидят некоторые изменения, которые были сделаны позже, но не увидят другие изменения, которые были сделаны ранее.

Проблемы 2 и 3 не возникают на x86, но могут возникать практически на любой другой реальной многоядерной/SMP-машине. Вы можете смягчить их с помощью некоторых специфичных для машины asm (или встроенных функций компилятора), чтобы вставить барьеры памяти в соответствующих точках.

person R.. GitHub STOP HELPING ICE    schedule 04.08.2011
comment
Не забывайте об атомарности — при чтении/записи величин, отличных от размера машинного слова (как меньшего, так и большего размера), или при чтении/записи смещенных значений вы можете увидеть несогласованное состояние объекта. - person Adam Rosenfield; 04.08.2011
comment
@ Адам Розенфилд, что вы подразумеваете под несогласованным состоянием? Это похоже на внеочередные изменения? - person Breakthrough; 04.08.2011
comment
@Breakthrough: это означает, что при некоторых условиях теоретически возможно на некоторых машинах, что некоторые данные были изменены, а некоторые еще нет. Например, вы можете записать значение 0xabcd в ячейку памяти, содержащую 0x1234, а другой поток может прочитать 0xab34. На практике этого не произойдет, если вы не очень постараетесь сделать так, чтобы это не получилось. На практике гарантируются одиночные (не R-M-W) операции до размера указателя, и, кроме того, реализации таковы, что строка кэша считывается, модифицируется и записывается обратно. Таким образом, вам действительно нужно проделать дополнительную работу, чтобы заставить его потерпеть неудачу. - person Damon; 04.08.2011
comment
В любом случае, для вашего конкретного примера это не имеет никакого значения. Самое худшее, что может случиться, это то, что ваше приложение отрисовывает слишком много объектов в том или ином кадре... ну и что. - person Damon; 04.08.2011
comment
@Damon, спасибо, это именно то, что я хотел услышать. Учитывая, что приложение работает минимум со скоростью 60 кадров в секунду, а я обновляю состояние примерно 30 раз в секунду, в этом нет ничего страшного. Последнее, скажем, я пытаюсь прочитать состояние как раз в тот момент, когда обновляется переменная... Что произойдет, если компьютер попытается одновременно читать и писать по одному и тому же адресу памяти? Это технически невозможно (поскольку есть только одна шина данных и одна адресная шина), но я просто хотел посмотреть, есть ли у вас какие-либо дополнительные входные данные. - person Breakthrough; 04.08.2011
comment
@Breakthrough: если машина не является SMP/многоядерной, нет возможности для одновременного доступа. На самом деле запланирован только один поток, и все очень просто. Если машина является SMP/многоядерной, процессор/ядро, выполняющее запись, обнаружит, что строка кэша используется совместно с другими процессорами/ядрами, и сделает их недействительными, остановив другие процессоры/ядра, пока происходит запись, и заставив их повторно загрузить весь строка кэша из основной памяти. - person R.. GitHub STOP HELPING ICE; 04.08.2011
comment
Кроме того... Обратите внимание, что то, что я описал, это то, как это работает на x86. Практически на любой другой архитектуре состояние кеша будет просто несовместимым между процессорами и ядрами. Вот что вызывает проблемы 2 и 3, которые я описал в своем ответе. Затем требуется явная инструкция барьера, чтобы принудительно аннулировать кэш. - person R.. GitHub STOP HELPING ICE; 04.08.2011
comment
А, спасибо за это замечание. Я согласен с возникновением проблем (2) или (3), поскольку время здесь не является большой проблемой. Я просто хотел быть уверенным, что никаких нарушений доступа к памяти или исключений не будет. - person Breakthrough; 04.08.2011

Элементы логического массива могут быть установлены/прочитаны с помощью атомарной операции, нет необходимости в мьютексе. Вам нужен мьютекс для защиты структуры, чтобы убедиться, что она остается согласованной. Поскольку ваши логические значения независимы, проблем нет.

person progrmr    schedule 04.08.2011
comment
Однако набор логических значений образует неатомарную структуру. Но в этом сценарии использования я бы сказал, что ОП безопасен. OP также может читать об алгоритмах без блокировки. - person datenwolf; 04.08.2011
comment
Именно, набор логических значений формировал бы неатомарную структуру, если бы к ним предъявлялись некоторые требования согласованности, и вам нужно было бы изменить несколько частей набора, не прерываясь в процессе. - person progrmr; 04.08.2011