代码之家  ›  专栏  ›  技术社区  ›  filo

如何在x86上使用gcc强制执行内存排序

  •  2
  • filo  · 技术社区  · 7 年前

    我想在线程(gcc、Linux、x86)之间共享一个数据结构。 假设线程A中有以下代码:

    shared_struct->a = 1;
    shared_struct->b = 1;
    shared_struct->enable = true;
    

    线程B是一个定期任务,它首先检查该结构的 enable 旗帜

    我认为编译器可以对线程A中的写操作重新排序,因此线程B可以看到不一致的数据。我熟悉ARM上的记忆障碍,但是 如何确保x86上的写入顺序? 有没有比 volatile ?

    我只想在结构中设置一个一致的状态,将所有内容“刷新”到内存中,并在最后设置一个启用标志。

    2 回复  |  直到 7 年前
        1
  •  3
  •   Basile Starynkevitch    7 年前

    你真的应该 使用互斥 (因为您提到了Pthread),所以添加 pthread_mutex_lock mtx; 内部字段 shared_struct (不要忘记使用 pthread_mutex_init )那么

    pthread_mutex_lock(&shared_struct->mtx);
    shared_struct->a = 1;
    shared_struct->b = 1;
    shared_struct->enable = true;
    pthread_mutex_unlock(&shared_struct->mtx);
    

    在访问共享数据的任何其他代码中也是如此。

    你也可以调查 atomic operations (但在你的情况下,你最好使用 mutex 如上图所示)。

    阅读一些 pthread tutorial .

    避免 race conditions undefined behavior .

    如何确保写入顺序

    你不能那样做 ,除非您正在实现一个线程库(其中一些部分应该在汇编程序中进行编码并使用 futex(7) ),如 nptl(7) 实施 pthreads(7) 在GNU中 glibc (或 musl-libc ). 您应该使用互斥体,并且不想浪费时间来实现线程库(所以请使用现有的线程库)。

    请注意,Linux上的大多数C标准库(包括glibc和musl libc)都是 free software ,因此您可以研究它们的源代码(如果您想了解Pthread互斥锁是如何实现的,等等)。

    编译器可以对写入进行重新排序

    它主要不是(当然不仅仅是)编译器,而是硬件。阅读关于 cache coherence . 操作系统也可能参与其中( futex(2) 有时由pthread mutex例程调用)。

        2
  •  2
  •   Peter Cordes    7 年前

    如果您只需要能够设置 enable = true 然后 stdatomic.h 通过发布/获取订购,您可以完全满足您的要求。(在x86 asm中,正常的存储/加载具有释放/获取语义,因此阻塞编译时重新排序就足够了 atomic volatile .)

    但是如果你想设置 enable = false 要在修改时再次“锁定”读卡器,则需要更复杂的更新模式。要么用atomics手动重新创建互斥体(坏主意;使用标准的库互斥体来代替它),要么在没有写入程序处于更新过程中时,允许多个读卡器进行无等待只读访问。

    任何一个 RCU a或 seqlock 这里可能很好 .

    对于seqlock,您有一个序列号,而不是启用=真/假标志。读卡器可以通过在读取其他成员之前检查序列号,然后在读取其他成员之后再次检查序列号来检测“撕裂”写入。(但所有成员必须 原子的 ,使用至少 mo_relaxed ,否则,即使丢弃该值,也只是在C中读取它们的数据竞争未定义行为。您还需要对检查计数器的负载进行足够的排序。e、 g.可能在第一个上获取,然后在 shared_struct->b 加载以确保序列号的第二次加载是在它之后进行的。( acquire 这只是一个单向的障碍:在轻松加载之后再进行获取加载并不能满足您的需要。)

    RCU使读者始终完全无需等待;它们只是取消对当前有效结构的指针的引用。更新就像原子化地替换指针一样简单。回收旧结构是变得复杂的地方:在重用内存块之前,必须确保每个读卡器线程都读取了内存块。


    简单设置 启用=false 在更改其他结构成员之前,不会阻止读者查看 enable == true 然后在编写器修改其他成员时,看到其他成员的值不一致/部分更新。如果您不需要这样做,但只发布新对象供其他线程访问,那么您描述的序列就可以了 atomic_store_explicit(&foo->enable, true, memory_order_release) .