内存屏障和volatile关键字足以避免数据竞争吗?

4

我想知道是否必须使用原子整数。

我有一个循环,看起来类似于这样:

struct loop {
  volatile int loop_variable;
  volatile int limit;
}

for (int i = loop.loop_variable ; i < loop.limit ; loop.loop_variable++) {

}

然后另一个线程执行以下操作:

loops.loop_variable = loops.limit;

并发问题需要解决内存屏障的问题。

这是线程安全的吗?

存在数据竞争的汇编代码在这两行之间:

// loop.loop_variable = loop.limit;
movl    4+loop.0(%rip), %eax                                                                                                                                                             
movl    %eax, loop.0(%rip)  

而且

    // for (int i = loop.loop_variable ; i < loop.limit ; loop.loop_variable++)
    movl    loop.0(%rip), %eax                                                                                                                                                               
    movl    %eax, -4(%rbp)                                                                                                                                                                   
    jmp .L2                                                                                                                                                                                  
.L3:                                                                                                                                                                                         
    movl    loop.0(%rip), %eax                                                                                                                                                               
    addl    $1, %eax                                                                                                                                                                         
    movl    %eax, loop.0(%rip)                                                                                                                                                               
.L2:                                                                                                                                                                                         
    movl    loop.0(%rip), %eax                                                                                                                                                               
    cmpl    $99999, %eax                                                                                                                                                                     
    jle .L3                                                                                                                                                                                  
    movl    $0, %eax  

可能存在数据竞争的地方是
movl    loop.0(%rip), %eax                                                                                                                                                               
        addl    $1, %eax                                                                                                                                                                         
        movl    %eax, loop.0(%rip)

因为增加循环变量需要三个指令,但只需要一个指令将循环变量覆盖到限制。


1
你有一个循环,它查看名为loop的结构体(可能是类型为struct loop的结构体?),然后一些代码查看loops[0]。它们是指同一个东西吗?(如果不是,答案可能会更容易;-)) - psmears
3
Volatile、内存屏障和竞态条件保护是用于三种不同目的的三种不同东西。如果你通常有三个工具:一把锯子,一个螺丝刀和一个锤子,那么你仍然不能用螺丝刀或锤子来锯木头。"但我既有螺丝刀又有锤子,为什么我不能锯木头呢?"因为你不能用这些工具来锯木头,也没有人声称你可以用它们来锯木头。 - Lundin
1
我想知道是否必须使用原子整型。您是在一个线程中编写它们,同时在另一个线程中读取或编写它们,而没有锁定或其他同步机制来确保这些操作不会“同时”发生吗?是的,您是这样做的,所以答案是肯定的,您必须使用原子类型。内存屏障对于操作何时相对于其他线程发生没有任何影响;只有当此线程内的操作何时对其他人可见时才会产生影响。 - Nate Eldredge
1
重要的是,volatile 没有豁免数据竞争规则。同时写入/读取 volatile 变量与没有使用 volatile 是一样的数据竞争,并且行为也同样未定义。在 <stdatomic.h> 存在之前的旧代码会使用 volatile,因为它是最接近的东西,并且如果您对编译器的优化了解得很多,它会大多数情况下工作。但在现代时代不合适。 - Nate Eldredge
显示剩余2条评论
1个回答

1

这个程序是多线程安全的吗?

不是。

已知:

loops[0].loop_variable = loops[0].limit;
< memory barrier >

在一个线程中,内存屏障并不能防止 int i = loop.loop_variable 读取到一个不确定的值,或者在另一个线程中 loop.loop_variable++ 产生无意义的结果。其他线程仍然可能“看到”对 loops[0].loop_variable 的更改,或者部分更改。
内存屏障只是在之后强制实现一致性 - 它不会在之前做任何事情。

如果您在循环迭代的末尾放置内存屏障会怎样呢?然后只是希望循环实际上会结束并且不会连续超过一次进行数据竞争。 - Samuel Squire
内存屏障不会在线程之间提供同步。它们所做的只是确保所有线程在执行后“看到”一致的内存视图。 - Andrew Henle

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