Какие типы данных по своей сути являются атомарными

Мне было интересно, какие типы данных в Go по своей сути потокобезопасны (если есть).

Мое предположение состоит в том, что int, float и bool безопасны, а составные типы — нет.

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

Спасибо.


person six fingered man    schedule 15.11.2014    source источник


Ответы (2)


Я не верю, что какой-либо из них гарантированно будет атомарным, но возможно, что некоторые из них на практике (конечно, под атомарным мы подразумеваем, что присвоение им одновременно из двух потоков приведет к одному или другое значение, а не какое-то третье значение (например, комбинацию битов из каждого значения) — мы не имеем в виду, что вы можете атомарно сравнивать и хранить или что-то в этом роде). Лучше всего ознакомиться с моделью Go Memory.

person joshlf    schedule 15.11.2014
comment
Да, это то, что я имею в виду под атомарностью, хотя я бы расширил ее до чтения одним потоком, пока другой пишет. Я проверю эту ссылку. Я немного удивлен, что цифры не имеют такой гарантии. - person six fingered man; 15.11.2014
comment
Проблема в том, что эти гарантии очень зависят от архитектуры, и вы фактически исключаете определенные архитектуры, создавая их. Например, если вы сказали, что 32-битные операции атомарны, то 16-битные процессоры никогда не смогут поддерживаться. Вдобавок к этому, откровенно говоря, это не то поведение, которое вы хотите поощрять. Если вам нужны потокобезопасные операции, эти вещи обрабатываются для вас пакетами sync и sync/atomic. Параллелизм действительно трудно сделать правильно, и лучше оставить это на усмотрение экспертов. - person joshlf; 15.11.2014
comment
(конечно, если вы эксперт или хотите поэкспериментировать, не стесняйтесь, но все же лучше держать эти вещи вне языковой спецификации) - person joshlf; 15.11.2014
comment
software.intel.com/en-us/blogs/2013/01/06/ - person JimB; 16.11.2014
comment
@JimB: Спасибо за эту ссылку. Хорошая информация. - person six fingered man; 20.11.2014

Просто примечание:

Изменение значения одного слова обычно должно быть атомарным, если вы запускаете код на той же платформе, на которой вы его скомпилировали. Но есть еще кое-что, что делает использование атомарных инструкций обязательным (если вы даже не планируете использовать более сильные гарантии).

Причина в том, что помимо переупорядочения памяти на уровне компилятора у вас может быть переупорядочение на уровне ЦП для оптимизации. (записи не распространяются сразу на основную память, ЦП имеет буферы хранения и т. д.).

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

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

В противном случае, если вы хотите сделать это атомарно, вы можете принять методологию чтения-копирования-обновления (ищите RCU или копирование при записи), но ОСТОРОЖНО! Если вы действительно не знаете, что делаете, вы можете очень легко попасть в беду. Это сложно сделать, когда у вас есть изменяемые объекты, вложенные в другие изменяемые объекты (поиск проблем, связанных с линеаризуемостью вложенных структур данных без блокировки). Это действительно сложно, и я не одобряю этого. Даже если вы добавите дополнительный уровень косвенности, чтобы ваши структуры данных выглядели неизменяемыми, решение проблемы параллельных атомарных чтений, обновлений и удалений — это материал уровня доктора философии. Если вам интересно, найдите диссертацию и статьи Александра Прокопца: http://axel22.github.io/home/professional/

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

person Taye    schedule 19.11.2014
comment
Спасибо за ответ. Конкретный сценарий, который я имел в виду, заключался в том, что обработчик HTTP-запроса изменяет uint32, который он закрывает. Поскольку запросы обрабатываются одновременно, у нас может быть два вызова обработчика, работающих с данными параллельно. Это будет работать на 32- или 64-битной машине (никогда не на 16-битной), и мне не нужно будет гарантировать, что все операции завершатся успешно, только то, что данные никогда не будут повреждены. Например, если обработчик просто увеличивает общий счетчик, если два увеличения происходят одновременно, было бы хорошо, если бы на самом деле произошло только одно увеличение. - person six fingered man; 20.11.2014
comment
Понимаю. Что ж, поскольку обработчик, вероятно, создается один раз и никогда не удаляется, я подозреваю, что вы могли бы либо использовать мьютекс, либо атомарно увеличить его без особого беспокойства. Причина в том, что вам по-прежнему нужна синхронизация между вашими базовыми кэшами, если вы работаете с GOMAXPROCS›1 (но канал здесь действительно был бы излишним). Это просто для того, чтобы у вас не было одной горутины, пытающейся выполнить 3++, в то время как другая пытается выполнить 5++. - person Taye; 20.11.2014
comment
Спасибо. Поскольку кажется, что я был немного оптимистичен в отношении его безопасности, и мое использование может быть не таким простым во всех случаях, я, вероятно, просто продолжу и использую мьютекс или посмотрю пакет sync/atomic, чтобы увидеть, что он предлагает. Спасибо еще раз! - person six fingered man; 20.11.2014