3

在英特尔的处理器手册:第 8.2.3.4 节中的链接中,声明可以将负载重新排序,早期存储到不同位置,但不能将早期存储重新排序到相同位置。

所以我明白以下两个操作可以重新排序:

x = 1;
y = z;

并且以下两个操作不能重新排序:

x = 1;
y = x;

但是当存储和负载位于不同的位置时会发生什么,但负载完全包含存储,例如:

typedef union {
  uint64_t shared_var;
  uint32_t individual_var[2];
} my_union_t;

my_union_t var;
var.shared_var = 0;

var.individual_var[1] = 1;
int y = var.shared_var;

那么在这种情况下'y'可以为0吗?

编辑(@Hans Passant)为了进一步解释这种情况,我试图看看我是否可以使用这种技术在不使用锁定指令的情况下设计一种线程之间的准同步。

所以一个更具体的问题是,给定一个全局变量:

my_union_t var;
var.shared_var = 0;

两个线程执行以下代码:

线程 1:

var.individual_var[0] = 1;
int y = __builtin_popcountl(var.shared_var);

线程 2:

var.individual_var[1] = 1;
int y = __builtin_popcountl(var.shared_var);

'y' 可以为两个线程都为 1 吗?

注意:__builtin_popcountl 是内置的 gcc 内在函数,用于计算变量中设置的位数。

4

2 回答 2

1

你最后也是最重要的问题1

两个线程执行以下代码:

线程 1:

var.individual_var[0] = 1;
int y = __builtin_popcountl(var.shared_var);

线程 2:

var.individual_var[1] = 1;
int y = __builtin_popcountl(var.shared_var);

'y' 可以为两个线程都为 1 吗?

是的,它可以,但如果不测试芯片是否会真正做到这一点,这并不明显,因为 SDM 中没有涵盖重叠读取。

这种情况基本上是 8.2.3.4(存储缓冲)和 8.2.3.5(存储转发)情况的组合。部分结果可能来自当前本地存储,而其余结果必须来自全局可见存储(即,来自“内存”)。

CPU 可以为您1提供两个线程的结果吗?的 - 一些当前的英特尔 CPU 将满足来自存储缓冲区的部分负载,以及来自 L1 的其余负载,但由于两个存储都尚未成为全局可见的(仍然位于存储缓冲区中),您可以进入var.iv[0] == 1 && var.iv[1] == 0线程1和var.iv[0] == 0 && var.iv[1] == 1线程2。

用户 Alex 实际上为此编写了测试代码,并在这个非常相关的答案中进行了演示。所以不,这里没有魔术:你不能像这样在所有 CPU 上构建自己的无锁同步。

顺便说一句:这可能适用于某些 CPU!在存在部分存储转发的情况下,一些模型可能会采取简单的做法,只是等到存储提交到 L1,然后从 L1 中读取整个值。在这种情况下,你的伎俩会奏效......但它最终不会让你买太多。您必须等待整个存储缓冲区耗尽,这是内存栅栏的主要成本!所以你得到了内存栅栏效应,最多是一个内存栅栏大小的停顿。


1前面单线程的答案“那么'y'在这种情况下可以为0吗?” case 显然是“不”——CPU 将保持按顺序执行的错觉,所以如果你写了一些东西并立即读回它,你总是会看到写的(没有其他线程写相同的位置),不管如何写入和读取重叠。

于 2018-06-01T01:46:19.730 回答
0

CPU 不知道也不关心您是否为内存位置设置了别名。因此,您的第一个问题的答案是“否”。

第二个示例中的写入未同步,因此,是的,线程可能拥有自己的数据副本。

您没有问的问题(“我应该实现和使用自定义同步原语吗?”)的答案是“不”。

于 2012-09-26T17:44:41.673 回答