简体   繁体   English

在C11 / C ++ 11中,可以在同一个内存中混合原子/非原子操作吗?

[英]In C11/C++11, possible to mix atomic/non-atomic ops on the same memory?

Is it possible to perform atomic and non-atomic ops on the same memory location? 是否可以在同一个内存位置执行原子操作和非原子操作?

I ask not because I actually want to do this, but because I'm trying to understand the C11/C++11 memory model. 我问不是因为我真的想这样做,而是因为我试图理解C11 / C ++ 11内存模型。 They define a "data race" like so: 他们定义了一个“数据竞争”,如下所示:

The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. 程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生。 Any such data race results in undefined behavior. 任何此类数据争用都会导致未定义的行为。 -- C11 §5.1.2.4 p25, C++11 § 1.10 p21 - C11§5.1.2.4P25,C ++ 11§1.10 p21基因

Its the "at least one of which is not atomic" part that is troubling me. 它的“至少有一个不是原子的”是困扰我的部分。 If it weren't possible to mix atomic and non-atomic ops, it would just say "on an object which is not atomic." 如果不可能混合原子和非原子操作,它只会说“在一个非原子的物体上”。

I can't see any straightforward way of performing non-atomic operations on atomic variables. 我看不到对原子变量执行非原子操作的任何直接方式。 std::atomic<T> in C++ doesn't define any operations with non-atomic semantics. C ++中的std::atomic<T>没有定义任何非原子语义的操作。 In C, all direct reads/writes of an atomic variable appear to be translated into atomic operations. 在C中,原子变量的所有直接读/写似乎都被转换为原子操作。

I suppose memcpy() and other direct memory operations might be a way of performing a non-atomic read/write on an atomic variable? 我想memcpy()和其他直接内存操作可能是对原子变量执行非原子读/写的方法吗? ie. 即。 memcpy(&atomicvar, othermem, sizeof(atomicvar)) ? memcpy(&atomicvar, othermem, sizeof(atomicvar)) But is this even defined behavior? 但这是否定义为行为? In C++, std::atomic is not copyable, so would it be defined behavior to memcpy() it in C or C++? 在C ++中, std::atomic是不可复制的,所以在C或C ++中将它定义为memcpy()吗?

Initialization of an atomic variable (whether through a constructor or atomic_init() ) is defined to not be atomic. 原子变量的初始化(无论是通过构造函数还是atomic_init() )被定义为不是原子的。 But this is a one-time operation: you're not allowed to initialize an atomic variable a second time. 但这是一次性操作:您不允许第二次初始化原子变量。 Placement new or an explicit destructor call could would also not be atomic. 放置新的或显式的析构函数调用也可能不是原子的。 But in all of these cases, it doesn't seem like it would be defined behavior anyway to have a concurrent atomic operation that might be operating on an uninitialized value. 但是在所有这些情况下,似乎不会定义行为,因为有一个可能在未初始化值上运行的并发原子操作。

Performing atomic operations on non-atomic variables seems totally impossible: neither C nor C++ define any atomic functions that can operate on non-atomic variables. 对非原子变量执行原子操作似乎完全不可能:C和C ++都没有定义任何可以对非原子变量进行操作的原子函数。

So what is the story here? 那么这里的故事是什么? Is it really about memcpy() , or initialization/destruction, or something else? 是真的关于memcpy() ,还是初始化/破坏,还是其他什么?

I think you're overlooking another case, the reverse order. 我认为你正在忽视另一个案例,相反的顺序。 Consider an initialized int whose storage is reused to create an std::atomic_int . 考虑一个初始化的int其存储被重用于创建std::atomic_int All atomic operations happen after its ctor finishes, and therefore on initialized memory. 所有原子操作都在ctor完成后发生,因此在初始化内存上发生。 But any concurrent, non-atomic access to the now-overwritten int has to be barred as well. 但是,对于现在被覆盖的int任何并发的非原子访问也必须被禁止。

(I'm assuming here that the storage lifetime is sufficient and plays no role) (我在这里假设存储寿命足够,不起作用)

I'm not entirely sure because I think that the second access to int would be invalid anyway as the type of the accessing expression int doesn't match the object's type at the time ( std::atomic<int> ). 我不完全确定,因为我认为第二次访问int无论如何都是无效的,因为访问表达式int的类型与当时对象的类型( std::atomic<int> )不匹配。 However, "the object's type at the time " assumes a single linear time progression which doesn't hold in a multi-threaded environment. 但是,“对象的在时刻类型”假定它不在多线程环境中保持单个线性时间进展。 C++11 in general has that solved by making such assumptions about " the global state" Undefined Behavior per se, and the rule from the question appears to fit in that framework. C ++ 11在总体上是解决了作出有关“ 全局状态”未定义行为本身这样的假设,并从问题的规定似乎适合在该框架内。

So perhaps rephrasing: if a single memory location contains an atomic object as well as a non-atomic object, and if the destruction of the earliest created (older) object is not sequenced-before the creation of the other (newer) object, then access to the older object conflicts with access to the newer object unless the former is scheduled-before the latter. 所以也许改写:如果一个内存位置包含一个原子对象以及一个非原子对象,并且如果在创建另一个(较新的)对象之前对最早创建的(较旧的)对象的破坏没有排序,那么对旧对象的访问与对较新对象的访问冲突,除非前者是在后者之前安排的。

disclaimer: I am not a parallelism guru. 免责声明:我不是并行大师。

Is it possible to mix atomic/non-atomic ops on the same memory, and if so, how? 是否有可能在同一个内存中混合原子/非原子操作,如果是这样,怎么样?

you can write it in the code and compile, but it will probably yield undefined behaviour. 你可以在代码中编写并编译,但它可能会产生未定义的行为。

when talking about atomics, it is important to understand what kind o problems do they solve. 在谈论原子论时,重要的是要了解他们解决了哪些问题。

As you might know, what we call in shortly "memory" is multi-layered set of entities which are capable to hold memory. 您可能知道,我们在短时间内称之为“内存”的是一组能够保存内存的实体。
first we have the RAM, then the cache lines , then the registers. 首先我们有RAM,然后是缓存行,然后是寄存器。

on mono-core processors, we don't have any synchronization problem. 在单核处理器上,我们没有任何同步问题。 on multi-core processors we have all of them. 在多核处理器上我们拥有所有这些。 every core has it own set of registers and cache lines. 每个核心都有自己的寄存器和缓存行。

this casues few problems. 这几乎没有问题。

First one of them is memory reordering - the CPU may decide on runtime to scrumble some reading/writing instructions to make the code run faster. 其中第一个是内存重新排序 - CPU可以决定运行时来搜索一些读/写指令以使代码运行得更快。 this may yield some strange results that are completly invisible on the high-level code that brought this set of instruction. 这可能会产生一些奇怪的结果,这些结果在带来这组指令的高级代码中完全不可见。
the most classic example of this phenomanon is the "two threads - two integer" example: 这个phenomanon最典型的例子是“两个线程 - 两个整数”的例子:

int i=0;
int j=0;
thread a -> i=1, then print j
thread b -> j=1 then print i;

logically, the result "00" cannot be. 从逻辑上讲,结果“00”不可能。 either a ends first, the result may be "01", either b ends first, the result may be "10". 或者首先结束,结果可以是“01”,或者b先结束,结果可以是“10”。 if both of them ends in the same time, the result may be "11". 如果它们都在同一时间结束,则结果可能是“11”。 yet, if you build small program which imitates this situtation and run it in a loop, very quicly you will see the result "00" 然而,如果你构建了一个小程序来模仿这个位置并在一个循环中运行它,非常quicly你会看到结果“00”

another problem is memory invisibility. 另一个问题是记忆隐形。 like I mentioned before, the variable's value may be cached in one of the cache lines, or be stored in one of the registered. 就像我之前提到的,变量的值可以缓存在其中一个缓存行中,或者存储在其中一个注册的缓存行中。 when the CPU updates a variables value - it may delay the writing of the new value back to the RAM. 当CPU更新变量值时 - 它可能会延迟将新值写回RAM。 it may keep the value in the cache/regiter because it was told (by the compiler optimizations) that that value will be updated again soon, so in order to make the program faster - update the value again and only then write it back to the RAM. 它可能会保留缓存/注册表中的值,因为它被告知(通过编译器优化)该值将很快再次更新,因此为了使程序更快 - 再次更新值,然后再将其写回内存。 it may cause undefined behaviour if other CPU (and consequently a thread or a process) depends on the new value. 如果其他CPU(以及线程或进程)依赖于新值,则可能导致未定义的行为。

for example, look at this psuedo code: 例如,看看这个伪代码:

bool b = true;
while (b) -> print 'a'
new thread -> sleep 4 seconds -> b=false;

the character 'a' may be printed infinitly, because b may be cached and never be updated. 字符'a'可以无限打印,因为b可能被缓存而且永远不会被更新。

there are many more problems when dealing with paralelism. 在处理诉讼时,还有很多问题。

atomics solves these kind of issues by (in a nutshell) telling the compiler/CPU how to read and write data to/from the RAM correctly without doing un-wanted scrumbling (read about memory orders ). atomics通过(简而言之)告诉编译器/ CPU如何正确地从RAM读取数据和从RAM读取数据而不做不想要的scrling(读取内存命令 )来解决这些问题。 a memory order may force the cpu to write it's values back to the RAM, or read the valuse from the RAM even if they are cached. 内存顺序可能会强制cpu将其值写回RAM,或者从RAM中读取值,即使它们被缓存。

So, although you can mix non atomics actions with atomic ones, you only doing part of the job. 因此,虽然您可以将非原子动作与原子动作混合,但您只能完成部分工作。

for example let's go back to the second example: 例如,让我们回到第二个例子:

atomic bool b = true;
while (reload b) print 'a'
new thread - > b = (non atomicly) false. 

so although one thread re-read the value of b from the RAM again and again but the other thread may not write false back to the RAM. 因此,虽然一个线程一次又一次地从RAM重新读取b的值,但另一个线程可能不会将false写回RAM。

So although you can mix these kind of operations in the code, it will yield underfined behavior. 因此,虽然您可以在代码中混合使用这些类型的操作,但它会产生不明确的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C ++ 11使用非原子变量的原子内存顺序 - C++11 Atomic memory order with non-atomic variables 在C ++ 11和OpenMP中以原子方式访问非原子内存位置? - Atomic access to non-atomic memory location in C++11 and OpenMP? 标准C ++ 11是否保证memory_order_seq_cst阻止StoreLoad在原子周围重新排序非原子? - Does standard C++11 guarantee that memory_order_seq_cst prevents StoreLoad reordering of non-atomic around an atomic? 在c ++ 11中,可以使用std :: atomic在两个线程之间传输非原子数据 - in c++11, can std::atomic be used to transmit non-atomic data between two thread 如何在 C++ 中混合原子和非原子操作? - How to mix atomic and non-atomic operations in C++? C++11 原子:std::memory_order 代码是否可移植? - C++11 atomic: is std::memory_order code portable? 在实践中,在C ++ 11中std :: atomic的内存占用量是多少? - What is the memory footprint of std::atomic in C++11, in practice? C ++ 11原子x86内存排序 - C++11 atomic x86 memory ordering 并发:C++11 内存模型中的原子性和易失性 - Concurrency: Atomic and volatile in C++11 memory model C ++编译器如何支持C ++ 11原子,但不支持C ++ 11内存模型 - How can C++ compilers support C++11 atomic, but not support C++11 memory model
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM