可以在没有互斥锁的情况下读取和验证共享内存吗?

Bri*_*les 6 c++ linux concurrency locking shared-memory

在Linux上,我正在使用shmgetshmat设置一个进程将写入的共享内存段,并且将读取一个或多个进程.正在共享的数据大小为几兆字节,并且在更新时完全重写; 它从未部分更新.

我有我的共享内存段如下:

    -------------------------
    | t0 | actual data | t1 |
    -------------------------

其中t 0和t 1是作者开始更新时的副本(具有足够的精度,以确保连续更新具有不同的时间).编写器首先写入t 1,然后复制数据,然后写入t 0.另一方面,读者读取t 0,然后读取数据,然后读取t 1.如果读者获得t 0和t 1的相同值,则它认为数据一致且有效,如果不是,则再次尝试.

这个程序是否确保如果读者认为数据有效,那么它实际上是?

我是否需要担心无序执行(OOE)?如果是这样,读者使用memcpy整个共享内存段是否会克服读者端的OOE问题?(这假设memcpy它是线性复制并通过地址空间上升.这个假设有效吗?)

jan*_*neb 5

现代硬件实际上是顺序一致的.因此,如果您不在适当的位置执行内存屏障,则无法保证这样做.需要障碍因为架构实现了比顺序一致性更弱的共享内存一致性模型.因此,这与流水线操作或OoO无关,而是允许多个处理器并行地高效访问存储器系统.请参阅例如共享内存一致性模型:教程.在单处理器上,您不需要障碍,因为所有代码都在该处理器上顺序执行.

此外,不需要有两个时间字段,序列计数可能是更好的选择,因为无需担心两个更新是否如此接近以至于它们获得相同的时间戳,并且更新计数器比获取计数器要快得多.当前时间.此外,时钟不可能在时间上向后移动,例如当ntpd调整时钟漂移时.虽然可以通过使用clock_gettime(CLOCK_MONOTONIC,...)在Linux上克服最后一个问题.使用序列计数器而不是时间戳的另一个好处是您只需要一个序列计数器.写入器在写入数据之前和写入完成之后递增计数器.然后读者读取序列号,检查它是否均匀,如果是,则读取数据,最后再次读取序列号并与第一个序列号进行比较.如果序列号是奇数,则表示正在进行写入,并且不需要读取数据.

Linux内核使用一个名为seqlocks的锁定原语来执行类似上面的操作.如果你不害怕"GPL污染",你可以谷歌实施; 因此,它是微不足道的,但诀窍是让障碍正确.


And*_*ass 4

Joe Duffy给出了完全相同的算法,并将其称为:“具有乐观重试的可扩展读取器/写入器方案”

有用。
您需要两个序列号字段。

您需要以相反的顺序读取和写入它们。
您可能需要设置内存屏障,具体取决于系统的内存排序保证

具体来说,当读取器和写入器分别访问 t 0或 t 1进行读取和写入时,您需要为读取器和写入器提供读取获取和存储释放语义。

需要什么指令来实现这一点取决于架构。例如,在 x86/x64 上,由于相对较强的保证,在这种特定情况下根本不需要机器特定的障碍*

* 仍然需要确保编译器/JIT 不会搞乱加载和存储,例如通过使用volatile(在 Java 和 C# 中与在 ISO C/C++ 中具有不同的含义。但是,编译器可能有所不同。例如使用 VC++ 2005或以上使用 volatile 执行上述操作是安全的。请参阅“Microsoft Specific”部分。它也可以在 x86/x64 上与其他编译器一起完成。应检查发出的汇编代码,并且必须确保访问 t 0和 t 1不会被编译器消除或移动。)

顺便说一句,如果您需要的话MFENCElock or [TopOfStack],0这可能是一个更好的选择,具体取决于您的需求