C ++ 20 / C ++ 2a (или как вы хотите это называть) добавит std::atomic_ref<T>
, что позволит вам выполнять атомарные операции над объектом, который не был atomic<T>
для начала.
недоступно пока как часть стандартной библиотеки для большинства компиляторов, но есть рабочая реализация для gcc / clang/ ICC / другие компиляторы с расширениями GNU.
Раньше атомарный доступ к «простым» данным был доступен только с некоторыми специфичными для платформы функциями, такими как Microsoft LONG InterlockedExchange(LONG volatile *Target, LONG Value);
или GNU C / C ++
type __atomic_add_fetch (type *ptr, type val, int memorder)
(те же встроенные функции, которые библиотеки C ++ для компиляторов GNU используют для реализации std::atomic<T>
.)
http://www.open -std.org / jtc1 / sc22 /wg21 / docs /apers / 2018 / p0019r8.html содержит некоторые вводные материалы о мотивации.Процессоры могут легко это сделать, компиляторы уже могут это сделать, и досадно, что C ++ не предоставляет эту возможность переносимым образом.
Поэтому вместо того, чтобы бороться с C ++, чтобы получить все неатомарное распределение и initСделано в конструкторе, вы можете просто при каждом доступе создавать atomic_ref для элемента, к которому вы хотите получить доступ.(Создавать его как локальный экземпляр можно, по крайней мере, когда он не блокируется, в любых «обычных» реализациях C ++).
Это даже позволит вам делать такие вещи, как изменение размера std::vector<int>
после того, как вы обеспечитедругие потоки не обращаются к элементам вектора или к самому блоку управления vector
.И тогда вы можете просигнализировать другим потокам о возобновлении.
Это еще не реализовано в libstdc ++ или libc ++ для gcc / clang.
#include <vector>
#include <atomic>
#define Foo std // this atomic_ref.hpp puts it in namespace Foo, not std.
// current raw url for https://github.com/ORNL/cpp-proposals-pub/blob/master/P0019/atomic_ref.hpp
#include "https://raw.githubusercontent.com/ORNL/cpp-proposals-pub/580934e3b8cf886e09accedbb25e8be2d83304ae/P0019/atomic_ref.hpp"
void inc_element(std::vector<int> &v, size_t idx)
{
v[idx]++;
}
void atomic_inc_element(std::vector<int> &v, size_t idx)
{
std::atomic_ref<int> elem(v[idx]);
static_assert(decltype(elem)::is_always_lock_free,
"performance is going to suck without lock-free atomic_ref<T>");
elem.fetch_add(1, std::memory_order_relaxed); // take your pick of memory order here
}
Для x86-64,они компилируются точно так, как мы надеемся с GCC, используя пример реализации (для компиляторов, реализующих расширения GNU), связанный в предложении рабочей группы C ++.https://github.com/ORNL/cpp-proposals-pub/blob/master/P0019/atomic_ref.hpp
Из проводника компилятора Godbolt с g ++ 8.2 -Wall -O3 -std=gnu++2a
:
inc_element(std::vector<int, std::allocator<int> >&, unsigned long):
mov rax, QWORD PTR [rdi] # load the pointer member of std::vector
add DWORD PTR [rax+rsi*4], 1 # and index it as a memory destination
ret
atomic_inc_element(std::vector<int, std::allocator<int> >&, unsigned long):
mov rax, QWORD PTR [rdi]
lock add DWORD PTR [rax+rsi*4], 1 # same but atomic RMW
ret
Атомная версия идентична, за исключениемон использует префикс lock
, чтобы сделать чтение-изменение-запись атомарным, , убедившись, что никакое другое ядро не может прочитать или записать строку кэша, пока это ядро находится в процессе атомарной модификации. Просто вЕсли вам интересно, как атомы работают в asm.
Большинство ISA, отличных от x86, таких как AArch64, конечно, требуют цикла повторения LL / SC для реализации атомарного RMW, даже с ослабленным порядком памяти.
Суть в том, что создание / уничтожение atomic_ref
ничего не стоит. Его указатель на член полностью оптимизируется.Так что это ровно так же дешево, как и vector<atomic<int>>
, но без головной боли.
Пока вы осторожны, не создавайте UB для гонки данных, изменяя размер вектора или получая доступэлемент без , проходящий через atomic_ref
.(Это потенциально может проявиться как использование после освобождения во многих реальных реализациях, если std :: vector перераспределяет память параллельно с другим потоком, индексирующим в нем, и, конечно, вы бы атомарно модифицировали устаревшую копию.)
Это, безусловно, дает вам возможность повеситься, если вы не будете уважать тот факт, что сам объект std::vector
не является атомарным, а также что компилятор не помешает вам сделать неатомарный доступ к основномуv[idx]
после того, как другие потоки начали использовать его.