Вам нужно сделать атомарный снимок всех членов структуры?Или вам просто нужен общий доступ на чтение / запись к отдельным членам по отдельности?Последнее намного проще, см. Ниже.
C11 stdatomic и C ++ 11 std :: atomic обеспечивают синтаксис для атомарных объектов произвольных размеров,Но если они больше, чем 8B или 16B, они не будут свободны от блокировки в типичных системах.(т.е. атомарная загрузка, хранение, обмен или CAS будут реализованы путем взятия скрытой блокировки и последующего копирования всей структуры).
Если вам просто нужна пара членов, вероятно, лучше использовать блокировку самостоятельно, а затемполучить доступ к членам, вместо того чтобы заставить компилятор скопировать всю структуру.(Текущие компиляторы не умеют оптимизировать странные способы использования таких атомик).
Или добавляет уровень косвенности , так что есть указатель, который можно легко атомарно обновить, чтобы он указывал на другойstruct
с другим набором значений. Это строительный блок для RCU (чтение-копирование-обновление) См. Также https://lwn.net/Articles/262464/. Существуют хорошие реализации библиотеки RCU, поэтому используйте один вместо того, чтобы свернуть свой собственныйесли ваш вариант использования намного проще, чем в общем случае.Выяснить, когда освобождать старые копии структуры, - одна из трудных частей, потому что вы не можете сделать это, пока последний поток чтения не закончится с этим.И весь смысл RCU состоит в том, чтобы сделать путь чтения как можно более легким ...
Ваша структура составляет 16 байтов в большинстве систем;едва достаточно маленький, чтобы x86-64 мог загружать или хранить все вещи несколько более эффективно, чем просто блокировка.(Но только с lock cmpxchg16b
).Тем не менее, не совсем глупо использовать атомарность C / C ++ для этой
, общей для C ++ 11 и C11:
struct A{
int a;
int b;
double c;
};
В C11 используйте тип _Atomic
классификатор для создания атомарного типа .Это такой классификатор, как const
или volatile
, поэтому вы можете использовать его практически для всего.
#include <stdatomic.h>
_Atomic struct A shared_struct;
// atomically take a snapshot of the shared state and do something
double read_shared (void) {
struct A tmp = shared_struct; // defaults to memory_order_seq_cst
// or atomic_load_explicit(&shared_struct, &tmp, memory_order_relaxed);
//int t = shared_struct.a; // UNDEFINED BEHAVIOUR
// then do whatever you want with the copy, it's a normal struct
if (tmp.a > tmp.b)
tmp.c = -tmp.c;
return tmp.c;
}
// or take tmp by value or pointer as a function arg
// static inline
void update_shared(int a, int b, double c) {
struct A tmp = {a, b, c};
//shared_struct = tmp;
// If you just need atomicity, not ordering, relaxed is much faster for small lock-free objects (no memory barrier)
atomic_store_explicit(&shared_struct, tmp, memory_order_relaxed);
}
Обратите внимание, что доступ к одному члену структуры _Atomic
- неопределенное поведение.Он не будет уважать блокировку и не может быть атомарным.Так что не делайте int i = shared_state.a;
(C ++ 11 не компилирует это, но C11 будет).
В C ++ 11 это почти то же самое : используйте std::atomic<T>
template.
#include <atomic>
std::atomic<A> shared_struct;
// atomically take a snapshot of the shared state and do something
double read_shared (void) {
A tmp = shared_struct; // defaults to memory_order_seq_cst
// or A tmp = shared_struct.load(std::memory_order_relaxed);
// or atomic_load_explicit(&shared_struct, &tmp, memory_order_relaxed);
//int t = shared_struct.a; // won't compile: no operator.() overload
// then do whatever you want with the copy, it's a normal struct
if (tmp.a > tmp.b)
tmp.c = -tmp.c;
return tmp.c;
}
void update_shared(int a, int b, double c) {
struct A tmp{a, b, c};
//shared_struct = tmp;
// If you just need atomicity, not ordering, relaxed is much faster for small lock-free objects (no memory barrier)
shared_struct.store(tmp, std::memory_order_relaxed);
}
Посмотрите его в проводнике компилятора Godbolt
Если вам не нужен снимок всей структуры, а вместо этого просто хотите, чтобы каждый член был отдельно атомарный , тогда вы можете просто сделать каждый член атомным типом.(Например, atomic_int
и _Atomic double
или std::atomic<double>
).
struct Amembers {
atomic_int a, b;
#ifdef __cplusplus
std::atomic<double> c;
#else
_Atomic double c;
#endif
} shared_state;
// If these members are used separately, put them in separate cache lines
// instead of in the same struct to avoid false sharing cache-line ping pong.
(Обратите внимание, что стандарт C11 stdatomic не гарантированно совместим с C ++ 11 std :: atomicпоэтому не ожидайте, что сможете получить доступ к той же самой структуре из C или C ++.)
В C ++ 11 struct-assignment для структуры с атомарными членами не будет компилироваться, потому что std::atomic
удаляет свой экземпляр-конструктор.(Вы должны загрузить std::atomic<T> shared
в T tmp
, как в приведенном выше примере с целой структурой.)
В C11, struct-assignment для неатомарной структуры с атомарными членами будет компилироваться, но не является атомным .Стандарт C11 нигде конкретно не указывает на это.Лучшее, что я могу найти: n1570: 6.5.16.1 Простое назначение:
В простом назначении (=) значение правого операнда преобразуется в тип выражения назначения и заменяет значениехранится в объекте, обозначенном левым операндом.
Поскольку это ничего не говорит о специальной обработке атомарных членов, следует предположить, что это похоже на memcpy
представлений объекта.(За исключением того, что разрешено не обновлять заполнение.)
На практике легко заставить gcc генерировать asm для структур с атомарным элементом, где он копирует неатомно.Особенно с большим атомарным элементом, который является атомным, но не свободным от блокировки.