我对 C++11/C11 内存模型有一些疑问,我想知道是否有人可以澄清。这些是关于模型/抽象机器的问题,而不是关于任何真实架构的问题。
- 获取/释放效果是否保证从一个线程“级联”到下一个线程?
这是我的意思的伪代码示例(假设所有变量都以 0 开头)
[Thread 1]
store_relaxed(x, 1);
store_release(a, 1);
[Thread 2]
while (load_acquire(a) == 0);
store_release(b, 1);
[Thread 3]
while (load_acquire(b) == 0);
assert(load_relaxed(x) == 1);
线程 3 的获取与线程 2 的发布同步,线程 2 的获取与线程 1 的发布同步。因此,线程 3 可以保证看到线程 1 设置为 x 的值,对吗?还是我们需要在这里使用 seq cst 以保证断言不会触发?我觉得获取/释放就足够了,但我找不到任何简单的解释来保证它。获取/释放的大部分解释主要集中在获取线程接收释放线程所做的所有存储。然而在上面的例子中,线程 2 从未接触过变量 x,而线程 1/线程 3 也不会接触到同一个原子变量。很明显,如果线程 2 要加载 x,它会看到 1,但是该状态是否保证会级联到其他线程,这些线程随后与线程 2 进行获取/释放同步?或者线程 3 是否也需要对变量 a 进行获取,以便接收线程 1 对 x 的写入?
根据https://en.cppreference.com/w/cpp/atomic/memory_order:
当前线程中的所有写入在获取相同原子变量的其他线程中都是可见的
释放相同原子变量的其他线程中的所有写入在当前线程中可见
由于线程 1 和线程 3 没有触及相同的原子变量,我不确定单独的获取/释放是否足以满足上述情况。正式描述中可能隐藏了一个答案,但我无法完全解决。
*编辑:直到事后才注意到,但在我发布的链接中有一个示例(“以下示例演示传递的发布-获取排序......”)与我的示例几乎相同,但它使用所有三个线程都使用相同的原子变量,这似乎很重要。我特意询问变量不相同的情况。
- 我是否相信根据标准,必须始终有一对非松弛原子操作,每个线程中都有一个,以便完全保证任何类型的内存排序?
想象有一个函数“get_data”,它分配一个缓冲区,向其中写入一些数据,并返回一个指向缓冲区的指针。还有一个函数“use_data”,它接受指向缓冲区的指针并对数据做一些事情。线程 1 从 get_data 获取缓冲区,并使用宽松的原子存储将其传递给线程 2 到全局原子指针。线程 2 在循环中放松原子加载,直到它获得指针,然后将其传递给 use_data:
int* get_data() {...}
void use_data(int* buf) {...}
int* global_ptr = nullptr;
[Thread 1]
int* buf = get_data();
super_duper_memory_fence();
store_relaxed(global_ptr, buf);
[Thread 2]
int* buf = nullptr;
while ((buf = load_relaxed(global_ptr)) == nullptr);
use_data(buf);
是否有任何类型的操作可以放在“super_duper_memory_fence”中,以保证在 use_data 获取指针时,缓冲区中的数据也是可见的?据我了解,没有可移植的方式来执行此操作,并且线程 2 必须具有匹配的栅栏或其他原子操作,以保证它接收到缓冲区中的写入,而不仅仅是指针值。这个对吗?