gpt4 book ai didi

C11 Atomic Acquire/Release 和 x86_64 缺乏加载/存储一致性?

转载 作者:行者123 更新时间:2023-12-03 15:50:48 25 4
gpt4 key购买 nike

我正在努力处理 C11 标准的第 5.1.2.4 节,特别是发布/获取的语义。我注意到 https://preshing.com/20120913/acquire-and-release-semantics/ (除其他外)指出:

... Release semantics prevent memory reordering of the write-release with any read or write operation that precedes it in program order.



因此,对于以下情况:
typedef struct test_struct
{
_Atomic(bool) ready ;
int v1 ;
int v2 ;
} test_struct_t ;

extern void
test_init(test_struct_t* ts, int v1, int v2)
{
ts->v1 = v1 ;
ts->v2 = v2 ;
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}

extern int
test_thread_1(test_struct_t* ts, int v2)
{
int v1 ;
while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v2 = v2 ; // expect read to happen before store/release
v1 = ts->v1 ; // expect write to happen before store/release
atomic_store_explicit(&ts->ready, true, memory_order_release) ;
return v1 ;
}

extern int
test_thread_2(test_struct_t* ts, int v1)
{
int v2 ;
while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v1 = v1 ;
v2 = ts->v2 ; // expect write to happen after store/release in thread "1"
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
return v2 ;
}

这些被执行的地方:
>   in the "main" thread:  test_struct_t ts ;
> test_init(&ts, 1, 2) ;
> start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
> start thread "1" which does: r1 = test_thread_1(&ts, 4) ;

因此,我希望线程“1”具有 r1 == 1,线程“2”具有 r2 = 4。

我希望是因为(遵循第 5.1.2.4 节的第 16 和 18 段):
  • 所有(非原子)读取和写入都是“先于顺序”,因此“发生在”线程“1”中的原子写入/释放之前,
  • 其中“线程间发生在”线程“2”中的原子读取/获取(当它读取“真”时),
  • 这反过来又是“先排序”,因此“发生在”(非原子)读取和写入(在线程“2”中)之前。

  • 然而,我完全有可能没有理解这个标准。

    我观察到为 x86_64 生成的代码包括:
    test_thread_1:
    movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
    test $0x1,%al
    jne <test_thread_1> -- while is true
    mov %esi,0x8(%rdi) -- (W1) ts->v2 = v2
    mov 0x4(%rdi),%eax -- (R1) v1 = ts->v1
    movb $0x1,(%rdi) -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
    retq

    test_thread_2:
    movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
    test $0x1,%al
    je <test_thread_2> -- while is false
    mov %esi,0x4(%rdi) -- (W2) ts->v1 = v1
    mov 0x8(%rdi),%eax -- (R2) v2 = ts->v2
    movb $0x0,(%rdi) -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
    retq

    如果 R1 和 X1 以该顺序发生,这将给出我期望的结果。

    但是我对 x86_64 的理解是,读取顺序发生,其他读取和写入顺序发生,其他写入顺序发生,但读取和写入可能不会彼此顺序发生。这意味着 X1 有可能在 R1 之前发生,甚至 X1、X2、W2、R1 也有可能按此顺序发生——我相信。 [这似乎非常不可能,但如果 R​​1 被某些缓存问题阻止?]

    请问:我有什么不明白的?

    我注意到,如果我更改 ts->ready 的加载/存储至 memory_order_seq_cst ,为商店生成的代码是:
      xchg   %cl,(%rdi)

    这与我对 x86_64 的理解一致,并将给出我期望的结果。

    最佳答案

    x86 的内存模型基本上是顺序一致性加上存储缓冲区(带有存储转发)。所以每个商店都是一个发布商店1。这就是为什么只有 seq-cst 存储需要任何特殊指令。 ( C/C++11 atomics mappings to asm )。另外,https://stackoverflow.com/tags/x86/info有一些指向 x86 文档的链接,包括 a formal description of the x86-TSO memory model (对于大多数人来说基本上是不可读的;需要涉足很多定义)。

    由于您已经阅读了 Jeff Preshing 出色的系列文章,我将向您指出另一篇更详细的文章:
    https://preshing.com/20120930/weak-vs-strong-memory-models/

    x86 上允许的唯一重新排序是 StoreLoad,而不是 LoadStore ,如果我们用这些术语说话。 (如果负载仅与存储部分重叠,存储转发可以做更多有趣的事情; Globally Invisible load instructions ,尽管您永远不会在编译器生成的 stdatomic 代码中得到它。)

    @EOF 评论了英特尔手册中的正确引用:

    Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B, 3C & 3D): System Programming Guide, 8.2.3.3 Stores Are Not Reordered With Earlier Loads.



    脚注 1:忽略弱排序的 NT 商店;这就是为什么你通常 sfence做完NT店之后。 C11/C++11 实现假定您没有使用 NT 存储。如果是,请使用 _mm_sfence在发布操作之前,以确保它尊重您的 NT 存储。 (一般来说 don't use _mm_mfence / _mm_sfence in other cases ;通常你只需要阻止编译时重新排序。或者当然只是使用 stdatomic。)

    关于C11 Atomic Acquire/Release 和 x86_64 缺乏加载/存储一致性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60138501/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com