Roc*_*ckU 15 c c++ struct atomic variable-assignment
我正在编写一个程序,其中一个进程读取和写入共享内存,另一个进程只读取它.在共享内存中有一个这样的结构:
struct A{
int a;
int b;
double c;
};
我期望的是立刻读取结构,因为在我阅读时,其他进程可能正在修改结构的内容.如果结构赋值是原子的,那么可以实现这一点,而不是中断.像这样:
struct A r = shared_struct;
那么,C/C++中的struct assignment atomic是什么?我尝试在网上搜索但找不到有用的答案.有人可以帮忙吗?谢谢.
sha*_*oth 17
不,C和C++标准都不保证赋值操作是原子的.你需要一些特定于实现的东西 - 编译器或操作系统中的东西.
您是否需要对所有结构成员进行原子快照?或者您是否只需要单独对不同成员进行共享读/写访问?后者要容易得多,见下文。
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;
};
Run Code Online (Sandbox Code Playgroud)
在 C11 中使用_Atomic
类型限定符来创建原子类型。它是一个像const
or一样的限定词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);
}
Run Code Online (Sandbox Code Playgroud)
请注意,访问结构的单个成员_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);
}
Run Code Online (Sandbox Code Playgroud)
如果您不需要对整个结构进行快照,而只是希望每个成员单独成为atomic,那么您可以简单地使每个成员成为原子类型。(喜欢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.
Run Code Online (Sandbox Code Playgroud)
(请注意,不保证 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
对象表示的 a 。(除了允许不更新填充。)
在实践中,很容易让 gcc 为具有原子成员的结构生成 asm,它以非原子方式复制。特别是对于一个原子但不是无锁的大原子成员。