Как std::atomic_ref реализован для неатомарных типов?
Мне интересно, как можно std::atomic_ref
быть реализованным эффективно (один std::mutex
на объект) для неатомарных объектов, поскольку следующее свойство кажется довольно сложным для соблюдения:
Атомарные операции, применяемые к объекту через atomic_ref, являются атомарными по отношению к атомарным операциям, применяемым через любые другие atomic_ref, ссылающиеся на тот же объект.
В частности, следующий код:
void set(std::vector<Big> &objs, size_t i, const Big &val) {
std::atomic_ref RefI{objs[i]};
RefI.store(val);
}
Кажется, довольно сложно реализовать, поскольку std::atomic_ref
нужно будет как-то каждый раз выбирать одно и то же std::mutex
(если только это не большая главная блокировка, используемая всеми объектами одного типа).
Я что-то пропустил? Или каждый объект отвечает за реализациюstd::atomic_ref
и поэтому либо атомарны, либо несут std::mutex
?
2 ответа
Реализация в значительной степени точно так же, какstd::atomic<T>
сам. Это не новая проблема.
См. Где находится блокировка для std::atomic? Типичная реализацияstd::atomic
/ std::atomic_ref
статическая хеш-таблица блокировок, индексированная по адресу, для неблокируемых объектов. Коллизии хэшей приводят только к дополнительным конфликтам, а не к проблеме корректности. (Тупиковые ситуации по-прежнему невозможны; блокировки используются только атомарными функциями, которые никогда не пытаются использовать две за раз.)
Например, в GCC std::atomic_ref
это просто еще один способ вызвать __atomic_store
на объекте. (См. Руководство GCC: встроенные атомарные команды)
Компилятор знает, T
достаточно мала, чтобы быть без блокировки. Если нет, он вызывает библиотечную функцию libatomic, которая будет использовать блокировку.
(забавный факт: это означает, что он работает, только если объект имеет достаточное выравнивание для atomic<T>
. Но на многих 32-битных платформах, включая x86,uint64_t
может иметь только 4-байтовое выравнивание. atomic_ref
на таком объекте будет компилироваться и запускаться, но на самом деле не будет атомарным, если компилятор использует 8-байтовую загрузку / сохранение SSE в 32-битном режиме для его реализации. К счастью, нет опасности для предметов,alignof(T) == sizeof(T)
, как и большинство примитивных типов на 64-битных архитектурах.)
Реализация может использовать хэш на основе адреса объекта, чтобы определить, какую из набора блокировок установить при выполнении операции.