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

How to enforce memory ordering with gcc on x86

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

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

线程 B 是一个周期性任务,它首先检查该结构的 enable 标志。

我认为编译器可以重新排序线程 A 中的写入,因此线程 B 可以看到不一致的数据。我熟悉 ARM 上的内存屏障,但是 我如何确保 x86 上的写入顺序? 有没有比 volatile 更好的方法?

我只是想在结构中设置一个一致的状态,"flush"所有内容都存入内存并在最后设置一个启用标志。

你真的应该使用互斥锁(因为你提到了 Pthread),所以在 shared_struct 中添加一个 pthread_mutex_lock mtx; 字段(不要忘记初始化它与 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 (but in your case, you'll better use a mutex,如上所示)。

阅读一些 pthread tutorial

避免race conditions and undefined behavior.

how do I ensure write ordering

你不会那样做,除非你正在实现一个线程库(然后它的某些部分应该用汇编程序编码并使用futex(7)), like nptl(7) implementation of pthreads(7) in GNU glibc (or musl-libc)。您应该使用互斥量并且您不想浪费时间实现线程库(因此使用现有的)。

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

the compiler can reorder the writes

主要不是(当然不仅仅是)编译器,而是硬件。阅读有关 cache coherence. And the OS may also be involved (futex(2) 有时由 pthread 互斥例程调用的信息。

如果您只需要能够设置 enable = true,那么 stdatomic.h 与发布/获取顺序可以满足您的要求。 (在 x86 asm 中,正常的 stores/loads 具有 release/acquire 语义,所以是的,阻止编译时重新排序就足够了。但是正确的方法是使用 atomic,而不是 volatile.)

但是如果你想在修改的时候能够再次设置enable = false到"lock out"readers,那么你需要一个更复杂的更新模式。要么用原子手动重新发明一个互斥锁(坏主意;使用标准库互斥锁代替那个),要么做一些允许多个 readers 在中间没有编写器时进行无等待只读访问的事情更新。

这里 RCU a or seqlock 都可以.

对于 seqlock,您有一个序列号,而不是 enable = true/false 标志。 reader 可以通过在读取其他成员之前和之后再次检查序列号来检测 "torn" 写入。 (但是所有成员都必须是 atomic,至少使用 mo_relaxed,否则即使在 C 中读取它们也是数据竞争未定义的行为,即使您丢弃该值。您还需要足够的顺序检查计数器的负载。例如,可能在第一个负载上获取,然后在 shared_struct->b 负载上获取,以确保序列号的第二个负载在它之后排序。(acquire 只是一个- 方式障碍:放松负载后的获取负载不会为您提供所需的东西。)

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


在更改其他结构成员之前简单地设置 enable = false 不会阻止 reader 查看 enable == true 然后看到其他成员的不一致/部分更新的值,同时作者正在修改它们。如果您不需要这样做,而只是释放新对象供其他线程访问,那么您描述的序列就可以 atomic_store_explicit(&foo->enable, true, memory_order_release).