首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在C11/C++11中,是否可以将原子/非原子操作混合在同一个内存中?

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

Stack Overflow用户
提问于 2016-01-31 01:43:59
回答 4查看 1.2K关注 0票数 10

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

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

如果程序在不同的线程中包含两个相互冲突的操作,其中至少一个不是原子的,并且在另一个线程之前也不会发生,那么程序的执行就包含了一个数据竞赛。任何这样的数据竞争都会导致未定义的行为。- C11§5.1.2.4 p25,C++11§1.10 p21

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

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

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

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

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

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2016-02-02 22:02:04

我觉得你忽略了另一个案子,相反的顺序。考虑一个初始化的int,它的存储被重用来创建一个std::atomic_int。所有原子操作都是在ctor完成之后,因此是在初始化内存上进行的。但是对现在覆盖的int的任何并发的、非原子的访问也必须被禁止。

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

我不完全确定,因为我认为对int的第二次访问无论如何都是无效的,因为访问表达式int的类型与当时的对象类型(std::atomic<int>)不匹配。但是,“对象在时间上的类型”假定在多线程环境中不存在单一的线性时间序列。一般来说,C++11已经通过对“全局状态”本身未定义的行为做出这样的假设来解决这个问题,而问题中的规则似乎适合于该框架。

因此,也许可以重新措辞:如果单个内存位置包含一个原子对象以及一个非原子对象,并且在创建另一个(较新的)对象之前,没有对最早创建的(旧的)对象的销毁进行排序,那么对旧对象的访问与对较新对象的访问冲突,除非前者被安排在后者之前。

票数 1
EN

Stack Overflow用户

发布于 2016-02-01 09:14:06

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

是否有可能将原子操作/非原子操作混合在同一个内存中,如果有,如何实现?

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

在谈论原子学时,了解它们能解决什么样的问题是很重要的。

正如您可能知道的,我们在短期内所称的“内存”是能够存储内存的多层实体集合。

首先我们有RAM,然后是缓存线,然后是寄存器。

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

这不算什么问题。

其中之一是内存重新排序-- CPU可能会决定运行时,以搜索一些读/写指令,以使代码运行得更快。这可能会产生一些奇怪的结果,这些结果在带来这组指令的高级代码中是完全不可见的。

这个现象最典型的例子是“两个线程-两个整数”示例:

代码语言:javascript
复制
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“。但是,如果您构建了模拟此情况并在循环中运行的小程序,您将看到结果"00“。

另一个问题是内存不可见。正如我前面提到的,变量的值可能缓存在缓存行中,或者存储在已注册的某个缓存行中。当CPU更新一个变量值时,它可能会延迟将新值写入RAM。它可能保留缓存/regiter中的值,因为编译器优化告诉它该值将很快再次更新,因此为了使程序更快--再次更新值,然后将其写回RAM。如果其他CPU (因此是线程或进程)依赖于新值,则可能会导致未定义的行为。

例如,查看这个psuedo代码:

代码语言:javascript
复制
bool b = true;
while (b) -> print 'a'
new thread -> sleep 4 seconds -> b=false;

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

在处理平行主义时还有更多的问题。

atomics通过(简单地说)告诉编译器/CPU如何正确地读取和从RAM中读取和写入数据,而不执行不需要的scrumbling (读取内存顺序)来解决这类问题。内存顺序可能会迫使cpu将其值写回RAM,或从RAM中读取值,即使它们是缓存的。

因此,尽管您可以将非原子操作与原子操作混合使用,但您只完成了部分工作。

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

代码语言:javascript
复制
atomic bool b = true;
while (reload b) print 'a'
new thread - > b = (non atomicly) false. 

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

因此,尽管您可以在代码中混合这类操作,但它会产生欠罚行为。

票数 0
EN

Stack Overflow用户

发布于 2022-11-14 23:31:47

我对这个主题很感兴趣,因为我有代码,有时我需要串行访问一系列地址,而在其他时候,我需要与管理争用的某种方式并行访问相同的地址。

所以,在并行代码中,不完全是由最初的问题(我认为)意味着并行的,或者几乎是并发的,原子和非原子操作所造成的情况,而是接近的。

我通过一些诡计多端的转换来说服我的C11编译器允许我访问一个整数,并且更有用的是一个指针,无论是原子的还是非原子的(“直接的”),已经确定了这两种类型在我的x86_64系统上都是正式无锁的。也就是说,原子类型和非原子类型的大小是相同的。

我绝对不会尝试将两种访问地址的方式混为一谈,这是注定要失败的。然而,我已经成功地在串行代码中使用了“直接”语法操作,在并行代码中使用了“原子”语法,这使我在串行和安全管理的并行争用中都能获得最快的访问(和更简单的语法)。

所以您可以这样做,只要您不尝试在并行代码中混合这两个方法,并且坚持使用无锁类型,这可能意味着指针的大小。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35109419

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档