如何在 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 在中间没有编写器时进行无等待只读访问的事情更新。
对于 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)
.
我想在线程之间共享一个数据结构(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 在中间没有编写器时进行无等待只读访问的事情更新。
对于 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)
.