我正在编写一个程序,其中一个进程读取和写入共享内存,另一个进程只读取它。在共享内存中有一个这样的结构:
struct A{
int a;
int b;
double c;
};
我期望立即读取结构,因为在我阅读时,其他进程可能正在修改结构的内容。如果结构赋值是原子的,那么可以实现这一点,而不是中断。像这样:
struct A r = shared_struct;
那么,C / C ++中的struct assignment atomic是什么?我尝试在网上搜索但找不到有用的答案。有人可以帮忙吗? 谢谢。
答案 0 :(得分:17)
不,C和C ++标准都不保证赋值操作是原子的。你需要一些特定于实现的东西 - 编译器或操作系统中的东西。
答案 1 :(得分:9)
C和C ++支持其当前标准中的原子类型。
C ++ 11引入了对atomic types的支持。同样,C11引入了atomics。
答案 2 :(得分:2)
您是否需要以原子方式快照所有结构成员?或者您是否只需要单独对单独成员进行共享读/写访问?后者更容易,见下文。
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中,具有原子成员的结构的struct-assignment不会被编译,因为std::atomic
删除了它的拷贝构造函数。 (您应该将std::atomic<T> shared
加载到T tmp
,就像上面的整个结构示例一样。)
在C11中,具有原子成员的非原子结构的struct-assignment将编译,但不是原子。 C11标准并没有特别指出这一点。我能找到的最好的是:n1570:6.5.16.1简单赋值:
在简单赋值(=)中,右操作数的值被转换为类型 赋值表达式并替换存储在左侧指定的对象中的值 操作数。
由于这并未说明原子成员的特殊处理,因此必须假定它与对象表示的memcpy
相似。 (除非它允许不更新填充。)
在实践中,很容易让gcc为具有原子成员的结构生成asm,其中它以非原子方式复制。特别是原子成员是原子的,但不是无锁的。
答案 3 :(得分:1)
不,不是。
这实际上是CPU架构相对于被击中的内存布局
的属性您可以使用“原子指针交换”解决方案,该解决方案可以是原子的,并且可以在无锁方案中使用。
确保将相应的共享指针(变量)标记为易失性,如果其他线程“立即”看到更改很重要 这在现实生活中(TM)不足以保证编译器正确处理。相反,当你想拥有无锁语义时,直接对原子原语/内在函数进行编程。 (请参阅评论和相关文章了解背景信息)
当然,相反,您必须确保在相关时间进行深层复印,以便在阅读方面进行处理。
现在所有这些在内存管理方面很快变得非常复杂,我建议你仔细检查你的设计,然后问自己认真所有(感知到的)性能优势是否能证明风险是合理的。你为什么不选择一个简单的(读/写)锁,或者开始使用线程安全的花哨的共享指针实现?