Безопасно использовать типы atomic из Си в динамических структурах?

При реализации одной нехитрой структуры данных, которая должна работать в многопоточном приложении, меня сильно насторожило обилие undefined behavior в описании стандарта stdatomic для языка Си. Даже при быстром поиске можно сразу же столкнутся с такими при инициализации переменной:

Calling this function on an atomic object that has already been initialized (either on construction or by calling this function earlier) causes undefined behavior


If obj was not default-constructed, the behavior is undefined.


И тут возникает вопрос. Допустим у нас есть структура, в которой есть atomic переменная. Выделяя память и инициализируя переменную, мы можем в дальнейшем использовать ее не сталкиваясь с undefined behavior. Но что если таких структур у нас много и их расположение в заранее определенной области памяти может меняться при работе?

Например, я хочу сделать стек, где элементы этого стека будут иметь заранее неизвестный размер (заголовок и следующий за ним произвольный массив). Каждый раз добавляя новый элемент (подчеркну, что в заранее выделенной памяти фиксированного размера), мы должны инициализировать атомарную переменную, а при удалении мы с ней ничего делать не должны, ибо соответствующая функция в стандарте отсутствует. В процессе работы и многократного создания/удаления элементов стека, мы каждый раз инициализируем новую атомарную переменную, которая может быть в памяти где угодно, включая те адреса, где раньше лежала старая переменная. Выходит мы можем инициализировать переменную дважды, а тогда, согласно стандарту, получим undefined behavior. Даже если мы исключаем возможность race condition, безопасно ли использовать атомарные переменные таким способом?

Честно говоря, я совершенно не понимаю, как происходит инициализация таких переменных в современных процессорах и компиляторах. Вероятно для некоторых случаев никакой специальной инициализации и вовсе не нужно, а для некоторых она создает блокировки.
  • Вопрос задан
  • 150 просмотров
Решения вопроса 1
@res2001
Developer, ex-admin
Атомарные переменные - это такие же переменные как и остальные. Инициализировать их вы можете сколько угодно раз. Это относится только к встроенным типам. Атомарные классы - это сказка.
Но на них накладываются некоторые ограничения:
1. выравнивание переменной - переменная должна находится по адресу обычно кратному размеру переменной. Если не будет правильного выравнивания, то intelовские процы могут сделать 2 операции чтения, что уже не будет атомарной операцией. Другие процы могут сгенерировать какое-нибудь исключение.
2. атомарная переменная всегда volatile - т.е. компилятор не может ее кэшировать в регистре, всегда идет обращение к памяти.
Не все атомарные переменные в std действительно атомарны. Это проверяется с помощью atomic_is_lock_free(). Атомарность зависит от платформы. Например на x86 int64 - не атомарна из-за ограничений процессора. Тогда как на 32 битных АРМах она вполне атомарна.
При обычном чтении/записи атомарных переменных (с упорядочиванием памяти memory_order_seq_cst) происходит синхронизация кэшей ядер - из-за чего атомарные операции "дороже" не атомарных. Но этот процесс на разных архитектурах стоит по разному. Для синхронизации кэшей есть отдельные ассемблерные инструкции.

В целом все undefined behavior для атомарных переменных в std связаны с:
1. возможностью объявить атомарным любой класс
2. не для всех встроенных типов на конкретной платформе гарантируется реальная атомарность (atomic_is_lock_free).
3. если вы не будете соблюдать ограничения, то же ничего гарантировать нельзя.
Так что особо пугаться undefined behavior не стоит. Просто соблюдайте ограничения, не используйте атомарные классы и удостоверьтесь, что встроенные типы на вашей платформе действительно атомарны.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы