如何在x86上使用gcc强制执行内存排序
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在x86上使用gcc强制执行内存排序相关的知识,希望对你有一定的参考价值。
我想在线程(gcc,Linux,x86)之间共享数据结构。假设我在线程A中有以下代码:
shared_struct->a = 1;
shared_struct->b = 1;
shared_struct->enable = true;
线程B是一个周期性任务,首先检查该struct是否为enable
标志。
我认为编译器可以重新排序线程A中的写入,因此线程B可以看到不一致的数据。我熟悉ARM上的内存障碍,但如何确保x86上的写入顺序?有没有比volatile
更好的方法?
我只想在结构中设置一致状态,将所有内容“刷新”到内存中并在结尾处设置启用标志。
你真的应该使用一个互斥量(因为你提到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)在GNU pthreads(7)(或glibc)中实现musl-libc。您应该使用互斥锁,并且您不希望浪费时间来实现线程库(因此请使用现有的线程库)。
请注意,Linux上的大多数C标准库(包括glibc和musl-libc)都是free software,因此您可以研究它们的源代码(如果您想了解如何实现Pthread互斥体等)。
编译器可以重新排序写入
它不是(当然不仅仅是)编译器,而是硬件。了解cache coherence。操作系统也可能涉及(futex(2)有时称为pthread互斥程序)。
如果您只需要能够设置enable = true
,那么带有发布/获取顺序的stdatomic.h
可以为您提供正确的要求。 (在x86 asm中,正常的存储/加载有释放/获取语义,所以阻止编译时重新排序就足够了。但正确的方法是使用atomic
,而不是volatile
。)
但是,如果您希望能够在修改时再次将enable = false
设置为“锁定”读取器,那么您需要更复杂的更新模式。要么用原子手动重新发明一个互斥锁(坏主意;使用标准的库互斥体而不是那个),或者在没有编写器处于更新过程中时,做一些允许多个读取器进行无等待只读访问的东西。
对于seqlock,而不是enable = true / false标志,您有一个序列号。读者可以通过在读取其他成员之前然后再检查序列号来检测“撕裂”写入。 (但是所有成员必须是atomic
,至少使用mo_relaxed
,否则它的数据竞争未定义行为只是从C中读取它们,即使您丢弃该值。您还需要对检查计数器的负载进行足够的排序。例如可能在第一个上获得,然后获取shared_struct->b
负载,以确保序列号的第二个负载在它之后排序。(acquire
只是一个单向屏障:放松负载后的获取负载不会给你你需要什么。)
RCU使读者始终完全等待;它们只是取消引用指向当前有效结构的指针。更新就像原子替换指针一样简单。回收旧结构是变得复杂的地方:在重用它之前,必须确保每个读取器线程都已读完一块内存。
在更改其他结构成员之前简单地设置enable = false
并不会阻止读者查看enable == true
,然后在编写者修改它们时查看其他成员的不一致/部分更新的值。如果您不需要这样做,但只发布新对象以供其他线程访问,那么您描述的序列与atomic_store_explicit(&foo->enable, true, memory_order_release)
一样好。
以上是关于如何在x86上使用gcc强制执行内存排序的主要内容,如果未能解决你的问题,请参考以下文章