当前位置: 首页 > 知识库问答 >
问题:

在原子获取加载之后,非原子加载是否可以重新排序?

萧允晨
2023-03-14

正如在C 11中所知,有6个内存顺序,在关于std::memory\u order\u acquire的文档中:

  • http://en.cppreference.com/w/cpp/atomic/memory_order

memory\u order\u acquire内存\u顺序\u获取

具有此内存顺序的加载操作对受影响的内存位置执行获取操作:在此加载之前,当前线程中的内存访问不能重新排序。这可以确保释放相同原子变量的其他线程中的所有写操作在当前线程中可见。

1、非原子加载可在原子获取加载后重新排序:

一、 e.不保证非原子负载在获取原子负载后不能重新排序。

static std::atomic<int> X;
static int L;
...

void thread_func() 
{
    int local1 = L;  // load(L)-load(X) - can be reordered with X ?

    int x_local = X.load(std::memory_order_acquire);  // load(X)

    int local2 = L;  // load(X)-load(L) - can't be reordered with X
}

可加载<代码>int local1=L 在X.load(标准::memory\u order\u acquire)之后重新排序

2、我们可以认为,在原子获取加载后,非原子加载不能重新排序:

一些文章包含一幅图片,展示了acquire-release语义的本质。这很容易理解,但可能会造成混淆。

例如,我们可能认为std::memory_order_acquire不能重新排序任何一系列Load-Load操作,即使是非原子加载也不能在原子获取加载后重新排序。

3、非原子加载可在原子获取加载后重新排序:

有一件好事需要澄清:Acquire语义可以防止read Acquire的内存重新排序,并按照程序顺序执行任何读或写操作。http://preshing.com/20120913/acquire-and-release-semantics/

但也知道:在强有序系统(x86、SPARC TSO、IBM大型机)上,发布-获取排序对于大多数操作是自动的。

Herb Sutter在第34页显示:https://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD哦!24884

4、也就是说,我们可以认为,在原子获取加载之后,非原子加载不能重新排序:

一、 e.对于x86:

  • 对于大多数操作,发布获取命令是自动的
  • 读取不会与任何读取一起重新排序。(任何-即,无论是否旧)

那么,在C 11中原子获取加载之后,非原子加载是否可以重新排序?

共有3个答案

王轶
2023-03-14
匿名用户

具有此内存顺序的加载操作对受影响的内存位置执行获取操作:在此加载之前不能重新排序当前线程中的内存访问。

这就像编译器代码生成的经验法则。

但这绝对不是C的公理。

有许多情况,有些很容易检测到,有些需要更多的工作,其中对内存Op on V的操作可以证明与对A的原子操作X重新排序。

最明显的两种情况:

  • 当V是一个严格的局部变量时:任何其他线程(或信号处理程序)都无法访问它,因为它的地址在函数之外不可用;
  • 当A是如此严格的局部变量时。

(请注意,编译器的这两次重新排序对于为X指定的任何可能的内存排序都是有效的。)

无论如何,转换是不可见的,它不会改变有效程序的可能执行。

在一些不太明显的情况下,这些类型的代码转换是有效的。有些是人为的,有些是现实的。

我很容易想出一个人为的例子:

using namespace std;

static atomic<int> A;

int do_acq() {
  return A.load(memory_order_acquire);
}

void do_rel() {
  A.store(0, memory_order_release);
} // that's all folks for that TU

注:

使用静态变量能够看到对对象的所有操作,对单独编译的代码;访问原子同步对象的函数不是静态的,可以从所有程序中调用。

作为同步原语,对建立与关系同步的操作:有一个:

  • 在pX点调用do\u rel()的线程X

a的修改顺序M定义得很好,对应于不同线程中对do\u rel()的调用。每次调用do\u acq()以下任一项:

  • 在pX\u i上观察调用do\u rel()的结果,并通过在pX\u i上拉入X的历史记录与线程X同步

另一方面,该值始终为0,因此调用代码仅从do\u acq()中获取0,无法从返回值中确定发生了什么。它可以先验地知道a的修改已经发生,但它不能只知道后验。先验知识可以来自另一个同步操作。先验知识是线程Y历史的一部分。无论哪种方式,获取操作都没有知识,也不会添加过去的历史:获取操作的已知部分是空的,它不能可靠地获取pY\u i上线程Y过去的任何内容。因此,对A的获取是无意义的,可以进行优化

换言之:当看到Y的历史中最新的、在A的所有修改之前的程序时,对M的所有可能值有效的程序必须有效。So do\u rel()一般不添加任何内容:do\u rel()可以在某些执行中添加非冗余的同步,但它添加的Y的最小值为零,因此正确的程序,没有竞争条件(表示为:其行为取决于M,例如其正确性是获得M允许值的某个子集的函数)的人必须准备好处理从do\u rel()获取任何信息;因此编译器可以使do\u rel()成为NOP。

[注意:参数行不容易推广到所有读取0并存储0的RMW操作。它可能不适用于acq rel RMW。换句话说,由于其“副作用”,acq rel RMW比单独的加载和存储更强大。]

总结:在该特定示例中,不仅存储器操作可以相对于原子获取操作上下移动,原子操作也可以完全删除。

宋丰
2023-03-14

您引用的参考非常清楚:您无法在加载之前移动读取。在您的示例中:

static std::atomic<int> X;
static int L;


void thread_func() 
{
    int local1 = L;  // (1)
    int x_local = X.load(std::memory_order_acquire);  // (2)
    int local2 = L;  // (3)
}

memory\u order\u acquire意味着(3)不能在(2)之前发生(2)中的加载在(3)中的加载之前排序)。它没有提到(1)和(2)之间的关系。

梁宪
2023-03-14

我相信这是在C标准中推理示例的正确方法:

  1. 加载(std::memory\u order\u acquire)(我们称之为“操作”(operation)(A))可以与X上的某个释放操作同步(operation)(R))-大致上,将值分配给X的操作,该操作正在读取

[原子顺序]/2对原子对象执行释放操作的原子操作A与对原子对象M执行获取操作的原子操作B同步,并从以A为首的释放序列中的任何副作用中获取其值。

这种与关系同步可能有助于在对L的某些修改和赋值之间建立发生在前的关系。如果对<代码>L<代码>的修改发生在<代码>(R)<代码>之前,那么,由于<代码>(R)<代码>与<代码>(A)<代码>同步,并且<代码>(A)<代码>在读取<代码>L<代码>之前排序,对<代码>L<代码>的修改发生在读取<代码>L<代码>之前。

但对赋值没有任何影响。它既不会导致涉及此分配的数据争用,也不会帮助阻止它们。如果程序是无种族的,那么它必须使用其他机制来确保L的修改与此读取同步(如果它不是无种族的,那么它表现出未定义的行为,标准对此没有进一步的说明)。

在C标准的四个角落内谈论“指令重新排序”是没有意义的。人们可能会谈论由特定编译器生成的机器指令,或者这些指令由特定CPU执行的方式。但从标准的角度来看,这些仅仅是不相关的实现细节,只要该编译器和该html" target="_blank">CPU产生与标准描述的抽象机器的一种可能执行路径一致的可观察行为(As-if规则)。

 类似资料:
  • 标准C 11是否保证防止StoreLoad围绕非原子内存访问的原子操作重新排序? 众所周知,C 11中有6个d::memory_order,它指定了如何围绕原子操作对常规的非原子内存访问进行排序-工作草案,编程语言标准C 2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf §29.3秩序和一致性

  • 我目前正在开发一个应用程序,可以在用户手机上找到所有MP3,然后将它们放入列表中。这是非常好的工作,非常快,即使有很多歌曲。现在,我为列表中的每个项目填充一个新列表,然后将其显示在我的recyclerview中。问题是,我的手机上有700首歌曲,这在相当长的一段时间内会阻塞UI线程。 现在,我想使用回收站视图不将列表中的所有项目一次加载到对象中,而是仅在它们即将显示时加载-但我不知道如何做到这一点

  • 问题内容: 目前,我正在处理一个包含子模块并使用numpy / scipy的python项目。Ipython用作交互式控制台。不幸的是,我对现在使用的工作流程不是很满意,请多多指教。 在IPython中,该框架是通过一个简单的命令加载的。但是,通常有必要在框架的子模块之一中更改代码。至此,一个模型已经加载完毕,我使用IPython与之交互。 现在,该框架包含许多相互依赖的模块,即,在最初加载框架时

  • 我知道下一个场景:(奇怪的格式,我知道) 如果线程#1和线程#2在完全相同的时间输入,这将发生: > 两者都将执行" CMPXCHG指令同时对两个线程生效: 3.1锁定前缀本机使用 3.2线程#1或#2首先到达,赢得比赛。 3.3获胜线程比较(是aBoolean==true?)这将返回"true",因此一个布尔值将被设置为"false"。 3.4 aBoolean现在为false。 3.5线程丢失

  • 我有一个ionic单页web应用程序,用户在其中完成一些信息并动态创建一些组件(新列、卡片等)。单击按钮后,我想在初始状态下完全重新加载页面。我尝试过角度生命周期挂钩,但似乎没有触发它们,因为路线没有变化。我也尝试了这条路线。导航(['home'),但它不起作用。我不需要保存任何数据。只需重新加载所有内容。

  • 问题内容: 目标:实施标准的“设置” GUI窗口。类别位于 ListView左侧,而相应选项位于Pane右侧。 (请忽略具有重复类别的明显错误;仍在处理) 在此处输入图片说明 我有一个用于整体“设置”窗口的主窗口,其中包含 ListView带有所有类别的设置。窗口的右侧 具有AnchorPane, 当从列表中选择一个类别时,用于为每个类别加载单独的FXML文件。 当用户选择类别时,我需要他们能够编