6

在 C++0x 中原子变量的文档之一中,在描述内存顺序时,它提到:

发布-获取订购

在强排序系统(x86、SPARC、IBM 大型机)上,发布-获取排序是自动的。此同步模式不会发出额外的 CPU 指令,只会影响某些编译器优化...

首先,x86 遵循严格的内存排序是真的吗?总是强加这一点似乎效率很低。意味着每次写入和读取都有围栏?

另外,如果我有一个对齐的 int,在 x86 系统上,原子变量是否有任何用途?

4

4 回答 4

12

是的,x86 确实有严格的内存排序,请参阅Intel 手册第 3A 卷第 8.2 章。较旧的 x86 处理器(例如 386)提供了真正严格的排序(称为强排序)语义,而更现代的 x86 处理器在少数情况下稍微宽松的条件,但您无需担心。例如,奔腾和 486 允许读取缓存未命中在写入缓存命中时先于缓冲写入(因此与读取的地址不同)。

是的,它可能效率低下。有时,高性能软件仅针对内存排序要求较宽松的其他架构而编写。

是的,原子变量在 x86 上仍然有用。它们与编译器具有特殊的语义,因此典型的读-修改-写操作以原子方式发生。如果您有两个线程同时递增一个原子变量(我的意思是std::atomic<T>C++11 中的类型变量),您可以确信该值会大 2;如果没有std::atomic,您可能会得到错误的值,因为一个线程在执行增量时将当前值缓存在寄存器中,即使在 x86 上存储到内存是原子的。

于 2012-08-06T21:41:10.563 回答
5

确实,在 x86 上,所有存储都有释放,所有加载都有获取语义。

这不会也不应该影响您编写 C++ 的方式:要编写并发的、无竞争的代码,您必须使用std::atomic构造或锁。

架构细节的意思是,在 x86 上,只要您最多要求获取/释放顺序,就会为原子字大小类型的操作生成很少或没有额外代码。(但是,顺序一致性会发出mfence指令。)但是,您仍然必须使用 C++ 原子类型,并且不能仅仅为了获得正确、格式良好的程序而忽略它们。原子变量的一个重要特性是它们可以防止编译器重新编码,这对于程序的正确性至关重要。

(在 C++11 之前,您将不得不使用编译器提供的扩展,例如 GCC 的__sync_*函数套件,这将使编译器正常运行。如果您真的想使用裸变量,您至少必须插入编译器为自己设障。)

于 2012-08-06T21:23:00.240 回答
1

有一张很好的表格,列出了可能发生的不同重新排序操作,并且(例如)x86 很少做这些操作。其他架构(众所周知的 Alpha)几乎可以做任何事情。

由于内存模型是由标准定义的,x86 et al 本质上是兼容的。

您关于原子变量的问题的答案略有不同。对变量的任何修改都涉及竞争条件,因此当多个线程更新同一个变量时,可能会丢失更新。原子变量的定义使得它们是原子操作的正确类型,从而消除了这种竞争条件。所以他们的目的之一不是为了订购。

于 2012-08-06T21:19:15.937 回答
1

请注意,释放/获取语义不一定意味着在每条指令之后都有一个 mfence。在此处输入图像描述可以在@Adam Rosenfield 引用的手册或快速浏览Wikipedia中看到在x86 上的保留。尽管如此,x86 具有用于存储的释放语义和用于加载的获取语义。

来自 Kerrek SB 的回答:

架构细节的意思是,在 x86 上,只要您最多要求获取/释放顺序,就会为原子字大小类型的操作生成很少或没有额外代码。(不过,顺序一致性会发出 mfence 指令。)

请注意,顺序一致性是默认设置!(参见例如cppreference)。

这意味着...

#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;

void producer()
{
    std::string* p  = new std::string("Hello");
    ptr = p;
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr))
        ;
    assert(*p2 == "Hello"); // never fails
}

(x86 上的 g++ -std=c++11 -S -O3)

...实际上将导致在mfence生产者函数中发出一个,以解释上述 x86 ( 在此处输入图像描述) 上的松弛。

而对于...

#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;

void producer()
{
    std::string* p  = new std::string("Hello");
    ptr.store(p, std::memory_order_release);
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fails
}

(x86 上的 g++ -std=c++11 -S -O3)

...不会插入 mfence,因为 x86 具有用于存储的释放语义和用于加载的获取语义。

于 2016-04-01T14:33:52.593 回答