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

问题描述 投票:2回答:2

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

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

线程B是一个周期性任务,首先检查该struct是否为enable标志。

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

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

c gcc x86 pthreads memory-barriers
2个回答
3
投票

你真的应该使用一个互斥量(因为你提到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 conditionsundefined behavior

我如何确保写入顺序

你不这样做,除非你正在实现一个线程库(然后它的某些部分应该在汇编程序中编码并使用futex(7)),如nptl(7)在GNU pthreads(7)(或glibc)中实现musl-libc。您应该使用互斥锁,并且您不希望浪费时间来实现线程库(因此请使用现有的线程库)。

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

编译器可以重新排序写入

它不是(当然不仅仅是)编译器,而是硬件。了解cache coherence。操作系统也可能涉及(futex(2)有时称为pthread互斥程序)。


2
投票

如果您只需要能够设置enable = true,那么带有发布/获取顺序的stdatomic.h可以为您提供正确的要求。 (在x86 asm中,正常的存储/加载有释放/获取语义,所以阻止编译时重新排序就足够了。但正确的方法是使用atomic,而不是volatile。)

但是,如果您希望能够在修改时再次将enable = false设置为“锁定”读取器,那么您需要更复杂的更新模式。要么用原子手动重新发明一个互斥锁(坏主意;使用标准的库互斥体而不是那个),或者在没有编写器处于更新过程中时,做一些允许多个读取器进行无等待只读访问的东西。

无论是RCU a还是seqlock都可能在这里很好。

对于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)一样好。

© www.soinside.com 2019 - 2024. All rights reserved.