Атомная загрузка / хранение для ОС, отличных от BSD? - PullRequest
3 голосов
/ 11 июля 2010

Среди атомарных операций, предоставляемых BSD (как указано на справочной странице atomic (9)), есть atomic_load_acq_int() и atomic_store_rel_int().В поисках эквивалента для других ОС (например, читая справочную страницу atomic (3) для Mac OS X, справочную страницу atomic_ops (3C) для Solaris иInterlocked*() функций для Windows), кажется, нет никаких (очевидных) эквивалентов для просто атомарного чтения / записи int.

Это потому, что это подразумевается для тех ОС, которые читают / записываютдля int гарантированно будет атомарным по умолчанию?(Или вы должны объявить их volatile в C / C ++?)

Если нет, то как можно выполнять атомное чтение / запись int в этих ОС?

(Атомарное чтение можно смоделировать, возвращая результат атомарного добавления 0, но нет эквивалента для атомарного чтения.)

1 Ответ

4 голосов
/ 11 июля 2010

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

Различные компиляторы / библиотеки предоставляют разные утилиты для первого. Вот, например, встроенные функции GCC для доступа к атомарной памяти . Все они сводятся к генерации либо блоков сравнения и замены , либо связанных с нагрузкой / сохраняющих условия командных блоков в зависимости от поддержки платформы. Скомпилируйте ваш исходный код, скажем, -S для GCC и посмотрите сгенерированный ассемблер.

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

При всем этом выровнено чтение и запись одного слова являются атомарными на всех товарных платформах (кто-то поправит меня, если я ' м здесь не так). Поскольку int s меньше или равны размеру слова процессора, вы защищены (см. Ссылку на встроенные функции GCC выше).

Важно порядок операций чтения и записи. Вот где важна модель архитектуры памяти. Он диктует, какие операции могут и не могут быть переупорядочены аппаратными средствами. Например, обновление связанного списка - вы не хотите, чтобы другие процессоры видели новый связанный элемент, пока сам элемент не находится в согласованном состоянии. Могут потребоваться явные барьеры памяти (также часто называемые "заборами памяти"). Получить барьер гарантирует, что последующие операции не будут переупорядочены до барьера (скажем, вы прочитали указатель элемента связанного списка перед содержимым элемента), Release барьер гарантирует, что предыдущие операции не переупорядочены после барьера (вы пишете содержимое элемента перед написанием нового указателя ссылки).

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

Извините за длинный ответ. Надеюсь, это немного прояснит.

Edit:

Наступающий стандарт C ++ 0x наконец-то решает проблему параллелизма, см. Документ по модели памяти C ++ Ханса Бома для получения более подробной информации.

...