Должен ли я использовать барьер при доступе к статически инициализированной переменной? - PullRequest
2 голосов
/ 30 января 2020

В моей функции у меня есть две следующие строки кода:

static volatile uint64_t static_index = 0;
const uint64_t index = __sync_fetch_and_add(&static_index, 1, __ATOMIC_RELAXED);

Как видите, static_index распределяется между потоками, а index - для каждого потока. Меня беспокоит то, что инициализация stati c может быть переупорядочена с использованием этой переменной, но я не уверен, может ли это быть применено к статически (однократно) инициализированным переменным.

Достаточно ли __ATOMIC_RELAXED, чтобы избежать переупорядочения в этом случае? Или, может быть, я должен использовать __ATOMIC_RELEASE или даже __ATOMIC_SEQ_CST здесь?

Я ценю любую помощь, спасибо.

1 Ответ

2 голосов
/ 30 января 2020

Ваш инициализатор stati c является константой времени компиляции, поэтому вы можете (по крайней мере, на практике) рассчитывать на это хранилище stati c, уже содержащее 0 при запуске процесса.

(В частности, это будет здесь в BSS. Ненулевая константа будет означать, что она идет в разделе .data.)


Я почти уверен, что это также будет безопасно для непостоянный инициализатор.

Для переменной stati c с функциональной областью с непостоянным инициализатором первый поток, который входит в функцию, запускает инициализатор. Компиляторы обычно используют защитную переменную. Быстрый случай (уже инициализированный) включает загрузочную загрузку этой защитной переменной для проверки того, что stati c var уже инициализирован. В противном случае Atomi c RMW гарантирует, что инициализатор выполняется ровно в 1 потоке, а остальные ждут этого.

Но оставляя в стороне детали реализации: я не перепроверил, что стандарт говорит о stati c ВАРС. Но в потоке, который выполняет инициализацию, static volatile foo = x четко упорядочен перед RMW на нем, поэтому гарантированно произойдет раньше.

В других потоках он становится вопрос о том, могут ли они изменить порядок при инициализации stati c. Я думаю, что ответ на этот вопрос должен быть отрицательным, в противном случае вы могли бы получить данные UB для чтения или записи без встроенных атомов c.

Внутри одного потока вы можете посмотреть static foo = non_const; убедившись, что foo инициализирован. Даже если мы не тот поток, который выполняет инициализацию.

memory_order_release или acquire не имеет смысла как способ убедиться, что инициализация stati c была завершена до того, как атоми c RMW, если какая-то другая нить мчалась с нами. Это управляет порядком видимости наших операций из POV других потоков. Я почти уверен, что языковые правила просто требуют, чтобы RMW происходил после всего, что static foo = bar подразумевает (будь то инициализация или ожидание этого при необходимости) из-за упорядочения последовательности. Ничто иное не сделает смысл, если вы рассматриваете случай не-атоми c. Вы не можете позволить другим потокам когда-либо читать неинициализированную переменную.

(Обратите внимание, что C поддерживает только неконстантные инициализаторы для stati c в области функций. Только C ++ поддерживает это для глобальных переменных.)


Кстати, нет особых причин использовать устаревшие / устаревшие GNU C __sync встроенные : в руководстве написано: Они не должны использоваться для нового кода, который должен используйте взамен '__ atomic' вместо .

3-й аргумент для __sync встроенных команд - это не порядок памяти , это "необязательный список переменных, защищенных барьером памяти », который G CC игнорирует. Это __atomic_fetch_add, который принимает параметр порядка памяти.


или лучше для большинства случаев, C11 <stdatomic.h> для _Atomic static uint64_t static_index = 0; и измените его с помощью https://en.cppreference.com/w/c/atomic/atomic_fetch_add

atomic_fetch_add_explicit(&static_index, 1, memory_order_relaxed);

(или, если хотите, idx = static_index++;, но по умолчанию используется seq_cst, поэтому компиляция будет менее эффективной для ISA, отличных от x86.)

Вам не нужен volatile _Atomic, поэтому вы можете отбросить классификатор типа volatile. Используя volatile для hand-r olled relaxed atomics - , как правило, не рекомендуется , теперь, когда доступен C11 / C ++ 11, но если вы это сделаете, то простой доступ / доступ к хранилищу к volatile подобен _Atomi c с mo_relaxed.

...