我需要一个内存屏障吗?

4
在下面的C99示例中,即使启用-O2优化,buffer_full标志在读取或写入缓冲区后是否保证被设置?或者,我需要一个内存屏障来确保正确的排序?
我预计这将在一个对齐的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 的访问是否原子化”?原则上来说,是的。 - Iharob Al Asimi
我认为这也取决于平台。如果int是本地的,那么它就是原子性的,只要变量具有非对齐地址。 - LPs
有点像。我想确保永远不会读取空缓冲区,也永远不会写入满缓冲区。我最关心的是顺序。设置buffer_full是否会在编译器或硬件中显示在代码之前被重新排序? - PaulH
1个回答

4
我理解您的问题是询问编译器是否可以重新排列对buffer_full的赋值和fill_buffer()以及read_buffer()的调用。这样的优化(以及任何优化)仅在不改变程序的外部可观察行为时才允许。
在这种情况下,由于buffer_full具有外部链接,编译器可能无法确定优化是否被允许。如果fill_buffer()和use_buffer()函数的定义以及它们调用的每个函数的定义等都与writer_thread()和reader_thread()函数在同一翻译单元中,那么它可能能够这样做,但这取决于它们的实现。如果符合标准的编译器不能确定是否允许优化,则必须不执行该优化。
然而,鉴于您的命名意味着这两个函数将在不同的线程中运行,因此,如果没有同步操作(例如内存屏障),则不能确保一个线程将如何感知另一个线程执行的共享、非_Atomic、非volatile数据的修改的相对顺序。
此外,如果一个线程写入一个非原子变量并且另一个线程访问同一变量(读或写),则除非在每个可能的操作顺序中两者之间进行同步操作或原子操作,否则存在数据竞争。 volatile 变量在这里并没有真正帮助(参见为什么在多线程 C 或 C++ 编程中不认为 volatile 有用?)。然而,如果您将 buffer_full 设为原子变量,或者使用其上的原子读写操作来实现函数,则可以避免涉及该变量以及 buffer 的数据竞争(对于当前的代码结构)。

1
在上一条评论中,我意思是“volatile”,而不是“atomic”。请注意更正。 - PaulH
我在问题中添加了一个假设,即这是唯一访问“buffer”和“buffer_full”的2个线程。鉴于此,是否仍需要互斥锁?如果需要,您能帮助我理解为什么吗? - PaulH
@PaulH,@PaulH,将其中一个或两个变量设置为volatile可以确保一个线程对该变量的写入通过另一个线程的读取被看到,并确保不会对所标记的变量进行重新排序操作。然而,这似乎不会产生您实际想要的语义:阅读线程未能实际读取或写入线程未能实际写入真的可以吗?通常在这种构造中,您希望读取器等待直到它可以读取,或者写入器等待直到它可以写入。这就是互斥锁所提供的内容。 - John Bollinger
1
@PaulH,就标准而言,即使使用volatile对象,仍然存在数据竞争。对buffer_full的读取和修改是“冲突”的操作。如果这些操作由不同的线程执行,至少有一个不是原子性的,则在执行的每个可能的总体顺序中,除非在读取和写入之间进行同步操作,否则构成数据竞争。这比我之前写的要具体一些--我会更新我的答案。 - John Bollinger
1
@PaulH,我已经更新了我的答案。至于硬件乱序执行,如果您的程序没有展现出未定义行为(特别是包括任何由数据竞争引起的行为),那么符合规范的编译器将会生成在目标环境下表现符合规范的代码。CPU级别的行为是的问题,而不是你的问题——这也是选择C语言而不是汇编语言的原因之一。 - John Bollinger
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接