我想在线程(gcc,Linux,x86)之间共享数据结构。 假设我在主题A中有以下代码:
MAIL_DRIVER=mailgun
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@sandboxcc*****************.mailgun.org
MAIL_PASSWORD=******************
MAIL_ENCRYPTION=tls
线程B是一个周期性任务,它首先检查该结构是否为shared_struct->a = 1;
shared_struct->b = 1;
shared_struct->enable = true;
标志。
我认为编译器可以重新排序线程A中的写入,因此线程B可以看到不一致的数据。我熟悉ARM上的内存障碍,但如何确保x86上的写入顺序?有没有比enable
更好的方法?
我只想在结构中设置一致状态,将所有内容“刷新”到内存中并在结尾处设置启用标志。
答案 0 :(得分:3)
你真的应该使用互斥锁(因为你提到了Pthread),所以在pthread_mutex_lock mtx;
内添加一个shared_struct
字段(别忘了用{初始化它} {1}})然后
pthread_mutex_init
并且在访问该共享数据的任何其他代码中类似。
您也可以查看atomic operations(但在您的情况下,您最好使用上面显示的mutex)。
阅读一些pthread tutorial。
避免使用race conditions和undefined behavior。
如何确保写入顺序
你没有那样做,除非你正在实现一个线程库(然后它的某些部分应该在汇编程序中编码并使用futex(7)),例如{{ 3}}在GNU nptl(7)(或pthreads(7))中实现glibc。您应该使用互斥锁,并且不想浪费时间来实现线程库(因此请使用现有的线程库)。
请注意,Linux上的大多数C标准库(包括glibc和musl-libc)都是musl-libc,因此您可以研究它们的源代码(如果您想了解如何实现Pthread互斥锁等)。
编译器可以重新排序写入
它主要不是(当然不仅仅是)编译器,而是硬件。阅读free software。操作系统也可能涉及(cache coherence有时会被pthread互斥例程调用。)
答案 1 :(得分:2)
如果您只需要设置enable = true
,那么带有发布/获取排序的stdatomic.h
会为您提供您正在要求的内容。 (在x86 asm中,正常的存储/加载具有释放/获取语义,因此阻止编译时重新排序就足够了。但正确的方法是使用atomic
,而不是volatile
。)
但是,如果您希望能够将enable = false
设置为"锁定"读者在修改它时,你需要一个更复杂的更新模式。要么用原子手动重新发明一个互斥锁(坏主意;使用标准的库互斥体而不是那个),或者在没有编写器处于更新过程中时,做一些允许多个读取器进行无等待只读访问的东西。 / p>
对于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)
。