您是否需要对所有结构成员进行原子快照?还是您只需要分别对不同成员的共享读/写访问权限?后者要容易得多,见下文。
C11 stdatomic 和 C++11 std::atomic 为任意大小的原子对象提供语法。但是,如果它们大于 8B 或 16B,那么它们在典型系统上就不会是无锁的。 (即原子加载、存储、交换或 CAS 将通过获取隐藏锁然后复制整个结构来实现。
如果您只想要几个成员,最好自己使用锁然后访问成员,而不是让编译器复制整个结构。 (当前的编译器并不擅长优化此类原子的奇怪用途)。
或者添加一个间接级别,所以有一个指针可以很容易地自动更新以指向另一个具有不同值集的struct。 这是RCU (Read-Copy-Update) 的构建块 另请参阅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> 模板。
#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);
}
看on the Godbolt compiler explorer
如果您不需要对整个结构进行快照,而是只希望每个成员单独原子,那么您可以简单地使每个成员成为原子类型。 (如atomic_int 和_Atomic double or 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 中,具有原子成员的结构的结构赋值不会编译,因为 std::atomic 删除了它的复制构造函数。 (您应该将 std::atomic<T> shared 加载到 T tmp 中,就像上面的整体结构示例一样。)
在 C11 中,具有原子成员的非原子结构的结构赋值将编译,但 不是原子的。 C11 标准并没有在任何地方特别指出这一点。我能找到的最好的是:n1570:6.5.16.1 简单分配:
在简单赋值 (=) 中,右操作数的值被转换为
赋值表达式并替换左边指定的对象中存储的值
操作数。
由于这并没有说明原子成员的特殊处理,因此必须假定它就像对象表示的memcpy。 (除非允许不更新填充。)
在实践中,很容易让 gcc 为具有原子成员的结构生成 asm,并在其中以非原子方式复制。尤其是具有原子但不是无锁的大型原子成员。