繁体   English   中英

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

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

是否可以在同一个内存位置执行原子操作和非原子操作?

我问不是因为我真的想这样做,而是因为我试图理解C11 / C ++ 11内存模型。 他们定义了一个“数据竞争”,如下所示:

程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生。 任何此类数据争用都会导致未定义的行为。 - C11§5.1.2.4P25,C ++ 11§1.10 p21基因

它的“至少有一个不是原子的”是困扰我的部分。 如果不可能混合原子和非原子操作,它只会说“在一个非原子的物体上”。

我看不到对原子变量执行非原子操作的任何直接方式。 C ++中的std::atomic<T>没有定义任何非原子语义的操作。 在C中,原子变量的所有直接读/写似乎都被转换为原子操作。

我想memcpy()和其他直接内存操作可能是对原子变量执行非原子读/写的方法吗? 即。 memcpy(&atomicvar, othermem, sizeof(atomicvar)) 但这是否定义为行为? 在C ++中, std::atomic是不可复制的,所以在C或C ++中将它定义为memcpy()吗?

原子变量的初始化(无论是通过构造函数还是atomic_init() )被定义为不是原子的。 但这是一次性操作:您不允许第二次初始化原子变量。 放置新的或显式的析构函数调用也可能不是原子的。 但是在所有这些情况下,似乎不会定义行为,因为有一个可能在未初始化值上运行的并发原子操作。

对非原子变量执行原子操作似乎完全不可能:C和C ++都没有定义任何可以对非原子变量进行操作的原子函数。

那么这里的故事是什么? 是真的关于memcpy() ,还是初始化/破坏,还是其他什么?

我认为你正在忽视另一个案例,相反的顺序。 考虑一个初始化的int其存储被重用于创建std::atomic_int 所有原子操作都在ctor完成后发生,因此在初始化内存上发生。 但是,对于现在被覆盖的int任何并发的非原子访问也必须被禁止。

(我在这里假设存储寿命足够,不起作用)

我不完全确定,因为我认为第二次访问int无论如何都是无效的,因为访问表达式int的类型与当时对象的类型( std::atomic<int> )不匹配。 但是,“对象的在时刻类型”假定它不在多线程环境中保持单个线性时间进展。 C ++ 11在总体上是解决了作出有关“ 全局状态”未定义行为本身这样的假设,并从问题的规定似乎适合在该框架内。

所以也许改写:如果一个内存位置包含一个原子对象以及一个非原子对象,并且如果在创建另一个(较新的)对象之前对最早创建的(较旧的)对象的破坏没有排序,那么对旧对象的访问与对较新对象的访问冲突,除非前者是在后者之前安排的。

免责声明:我不是并行大师。

是否有可能在同一个内存中混合原子/非原子操作,如果是这样,怎么样?

你可以在代码中编写并编译,但它可能会产生未定义的行为。

在谈论原子论时,重要的是要了解他们解决了哪些问题。

您可能知道,我们在短时间内称之为“内存”的是一组能够保存内存的实体。
首先我们有RAM,然后是缓存行,然后是寄存器。

在单核处理器上,我们没有任何同步问题。 在多核处理器上我们拥有所有这些。 每个核心都有自己的寄存器和缓存行。

这几乎没有问题。

其中第一个是内存重新排序 - CPU可以决定运行时来搜索一些读/写指令以使代码运行得更快。 这可能会产生一些奇怪的结果,这些结果在带来这组指令的高级代码中完全不可见。
这个phenomanon最典型的例子是“两个线程 - 两个整数”的例子:

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

从逻辑上讲,结果“00”不可能。 或者首先结束,结果可以是“01”,或者b先结束,结果可以是“10”。 如果它们都在同一时间结束,则结果可能是“11”。 然而,如果你构建了一个小程序来模仿这个位置并在一个循环中运行它,非常quicly你会看到结果“00”

另一个问题是记忆隐形。 就像我之前提到的,变量的值可以缓存在其中一个缓存行中,或者存储在其中一个注册的缓存行中。 当CPU更新变量值时 - 它可能会延迟将新值写回RAM。 它可能会保留缓存/注册表中的值,因为它被告知(通过编译器优化)该值将很快再次更新,因此为了使程序更快 - 再次更新值,然后再将其写回内存。 如果其他CPU(以及线程或进程)依赖于新值,则可能导致未定义的行为。

例如,看看这个伪代码:

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

字符'a'可以无限打印,因为b可能被缓存而且永远不会被更新。

在处理诉讼时,还有很多问题。

atomics通过(简而言之)告诉编译器/ CPU如何正确地从RAM读取数据和从RAM读取数据而不做不想要的scrling(读取内存命令 )来解决这些问题。 内存顺序可能会强制cpu将其值写回RAM,或者从RAM中读取值,即使它们被缓存。

因此,虽然您可以将非原子动作与原子动作混合,但您只能完成部分工作。

例如,让我们回到第二个例子:

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

因此,虽然一个线程一次又一次地从RAM重新读取b的值,但另一个线程可能不会将false写回RAM。

因此,虽然您可以在代码中混合使用这些类型的操作,但它会产生不明确的行为。

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM