我需要内存屏障吗?

Posted

技术标签:

【中文标题】我需要内存屏障吗?【英文标题】:Do I need a memory barrier? 【发布时间】:2017-01-05 16:07:30 【问题描述】:

在下面的 C99 示例中,是否保证在读取或写入缓冲区后设置 buffer_full 标志(即使启用了 -O2 优化)?或者,我是否需要内存屏障来确保正确排序?

我希望这将在对齐的 32 位读取和写入是原子的系统上运行。

假设每个线程只有一个实例正在运行,并且没有其他线程正在访问bufferbuffer_full

char buffer[100];
int buffer_full;

// write interesting data to the buffer. does not read.
void fill_buffer(char* buffer, size_t buffsz);
// read the interesting data in the buffer. does not write.
void use_buffer(const char* buffer, size_t buffsz);

void writer_thread()

    if (!buffer_full) 
        fill_buffer(buffer, sizeof(buffer));
        // is a memory barrier needed here?
        buffer_full = 1;
    


void reader_thread()

    if (buffer_full) 
        use_buffer(buffer, sizeof(buffer));
        // is a memory barrier needed here?
        buffer_full = 0;
    

【问题讨论】:

您的问题有点难以理解,但我想您的意思是,可以访问buffer_full atomic?原则上是这样。 我认为也是平台相关的。如果int 是本机的,则为原子的,并且变量的地址未对齐。 有点。我想确保永远不会读取空缓冲区并且永远不会写入完整缓冲区。我主要关心的是订购。设置 buffer_full 是否会在编译器或硬件显示在代码中之前重新排序。 【参考方案1】:

我认为您是在询问编译器是否可以通过调用fill_buffer()read_buffer() 来重新排序对buffer_full 的赋值。只有在不改变程序的外部可观察行为的情况下,才允许这样的优化(以及任何优化)。

在这种情况下,因为buffer_full 具有外部链接,所以编译器不太可能确定是否允许优化。如果 fill_buffer()use_buffer() 函数的定义,以及它们自己调用的每个函数的定义,etc,它可能会这样做。与writer_thread()reader_thread() 函数在同一个翻译单元中,但这在某种程度上取决于它们的实现。如果符合标准的编译器不确定是否允许优化,则不得执行。

由于您的命名暗示这两个函数将在不同的线程中运行,但是,如果没有诸如内存屏障之类的同步操作,您就无法确定一个线程将感知的相对顺序> 对共享的、非_Atomic、非volatile 数据的修改由不同的线程执行。

此外,如果一个线程写入一个非原子变量,而另一个线程访问同一个变量(读取或写入),则存在数据竞争,除非同步操作或原子操作以所有可能的整体顺序在两者之间进行干预的操作。 volatile 变量在这里并没有真正的帮助(参见Why is volatile not considered useful in multithreaded C or C++ programming?)。但是,如果您将buffer_full 设为原子,或者如果您使用原子读写操作实现您的函数,那么这将有助于避免不仅涉及该变量,还涉及buffer 的数据竞争(对于您当前的代码结构)。

【讨论】:

谢谢!如果缓冲区或标志是原子的,会有所不同吗? 编辑:意识到我在上一条评论中说“易失性”时说的是“原子”。 我在问题中添加了一个假设,即这些是唯一访问bufferbuffer_full 的两个线程。鉴于此,是否仍需要互斥锁?如果是这样,你能帮我理解为什么吗? @PaulH, @PaulH, 使volatile 中的一个或两个将确保一个线程对该变量的写入可以通过另一个线程的读取来查看,并且将确保不会对so 上的操作进行重新排序-限定变量。但是,这似乎不太可能产生您真正想要的语义:读取器线程无法实际读取,或者写入器线程无法实际写入真的可以吗?通常在这样的结构中,您希望阅读器等待直到它可以读取,或者写入器等待直到它可以写入。这就是你从互斥体中得到的。 @PaulH,我已经更新了我的答案。至于硬件乱序执行,如果您的程序没有表现出未定义的行为(特别是包括由数据竞争引起的任何行为),那么符合标准的编译器将生成符合目标环境行为的代码。 CPU 级别的行为是 关心的,而不是你的 - 这是更喜欢 C 而不是汇编的原因之一。

以上是关于我需要内存屏障吗?的主要内容,如果未能解决你的问题,请参考以下文章

内存屏障

我是不是需要内存屏障来访问已完成的线程修改的内存?

解密内存屏障

解密内存屏障

内存屏障

Linux 内核 内存管理优化内存屏障 ④ ( 处理器内存屏障 | 八种处理器内存屏障 | 通用内存屏障 | 写内存屏障 | 读内存屏障 | 数据依赖屏障 | 强制性内存屏障 |SMP内存屏障 )