Я разрабатываю базу данных в памяти, и моей системе требуется большой массив std::atomic_int
объекты, которые примерно действуют как блокировки для записей базы данных. Теперь я бы предпочел распределить эти блокировки с помощью системных вызовов VM, таких как mmap
в Unix-подобных системах и VirtualAlloc
на Win32 / 64. Для этого есть несколько причин, и только одна из них не нуждается в явной инициализации памяти (т. Е. Память, выделенная системными вызовами ВМ, гарантированно обнуляется ОС). Итак, я хотел бы сделать это:
#include <sys/mman.h>
#include <atomic>
// ...
size_t numberOfLocks = ... some large number ...;
std::atomic_int* locks = reinterpret_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0));
// ... use locks[i].load() or locks[i].store() as with any memory order as appropriate
Мой главный вопрос — безопасен ли этот код? Я бы интуитивно ожидал, что код будет работать на любой разумной платформе с современным компилятором: mmap
гарантированно возвращает память, выровненную по границе страницы виртуальной машины, поэтому любые требования выравнивания std::atomic_int
должен быть удостоен чести, и конструктор std::atomic_int
не инициализирует значение, поэтому нет опасности не вызвать конструктор, так как длинные операции чтения и записи реализуются разумным способом (например, с использованием __atomic_*
Встроенные GCC и лязг).
Тем не менее, я могу себе представить, что этот код не обязательно безопасен в соответствии со стандартом C ++ — правильно ли я так думаю? Если это правильно, есть ли какая-либо проверка, чтобы, если код успешно компилируется на целевой платформе (то есть, если реализация std::atomic_int
это то, что я ожидаю, что это будет), тогда все работает, как я ожидаю?
В связи с этим, я ожидаю следующий код, где std::atomic_int
не выровнено свойство, чтобы сломать на x86:
uint8_t* region = reinterpret_cast<uint8_t*>(mmap(...));
std::atomic_int* lock = reinterpret_cast<std::atomic_int*>(region + 1);
lock->store(42, std::memory_order_relaxed);
Причина, по которой я думаю, что это не должно работать, заключается в том, что разумная реализация std::atomic_int::store
с std::memory_order_relaxed
на x86 это просто нормальный ход, который гарантированно будет атомарным только для доступа с выравниванием по словам. Если я прав в этом, могу ли я что-нибудь добавить в код, чтобы защитить от таких ситуаций и, возможно, обнаружить такие проблемы во время компиляции?
Это безопасно как mmap
выделяет память, соответствующим образом выровненную для любого встроенного и SIMD-типа.
Убедитесь, что вы звоните std::uninitialized_default_construct_n
(или ваш собственный эквивалент до C ++ 17), чтобы удовлетворить требования стандарта C ++, что конструктор должен быть вызван, и std::destroy_n
вызывать деструкторы после использования. Эти вызовы компилируются в 0 инструкций, потому что конструктор по умолчанию и деструктор std::atomic<>
являются тривиальный (ничего не делать):
size_t numberOfLocks = ... some large number ...;
auto* locks = static_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), ...));
// initialize
std::uninitialized_default_construct_n(locks, numberOfLocks);
// ... use ...
// uninitialize
std::destroy_n(locks, numberOfLocks);
Других решений пока нет …