同时访问易失性是数据竞争吗?尽管它在实践中似乎有效?

问题描述 投票:0回答:2
volatile global x = 0;
reader() {
  while (x == 0) {}
  print ("World\n");
}
writer() {
  print ("Hello, ")
  x = 1;
}
thread (reader);
thread (writer);

https://en.wikipedia.org/wiki/Race_condition#:~:text=Data%20race%5Bedit,仅%20atomic%20操作.

来自维基百科,

数据竞争的精确定义特定于正式的 正在使用并发模型,但通常它指的是一种情况 其中一个线程中的内存操作可能会尝试 在内存操作的同时访问内存位置 另一个线程正在写入该内存位置,在上下文中 这很危险。

  1. 至少有一个线程写入x。 (作家)
  2. 至少有一个线程读取x。 (读者)
  3. 没有任何同步机制来访问x。 (两个线程都访问x,没有任何锁。)

因此,我认为上面的代码是数据竞争。 (显然不是竞争条件) 我说得对吗?

那么当代码存在数据竞争但它生成了预期的输出时,数据竞争的含义是什么? (我们会看到“你好,世界 “,假设处理器保证对某个地址的存储对于存储指令之后发出的所有加载指令都可见)

---------- 添加了工作 cpp 代码 ------------

#include <iostream>
#include <thread>

volatile int x = 0;

void reader() {
    while (x == 0 ) {}
    std::cout << "World" << std::endl;
}

void writer() {
    std::cout << "Hello, ";
    x = 1;
}

int main() {
    std::thread t1(reader);
    std::thread t2(writer);
    t2.join();
    t1.join();
    return 0;
}
c++ concurrency language-lawyer atomic volatile
2个回答
6
投票

是的,这是一场数据竞赛和UB。

[intro.races]/2

如果两个表达式求值其中一个修改内存位置,而另一个表达式求值读取或修改同一内存位置,则会发生冲突。

[intro.races]/21

如果满足以下条件,则两个操作可能同时发生:
— 它们由不同的线程执行,...

...

程序的执行包含数据竞争,如果它包含两个潜在并发冲突的操作,至少其中一个不是原子的,并且两者都不会在另一个之前发生,...

任何此类数据竞争都会导致未定义的行为。

要使不同线程中的两件事“先发生”,必须涉及同步机制,例如非宽松原子、互斥锁等。


5
投票

是的,C++ 中存在数据竞争以及因此未定义的行为。未定义的行为意味着您无法保证程序将如何运行。看到“预期”输出是一种可能的输出,但不能保证它会发生。

这里

x
是非原子的,由线程
t1
读取并由线程
t2
写入,没有任何同步,因此它们会导致数据争用。

volatile
对于访问是否是数据竞争没有影响。仅使用原子(例如
std::atomic<int>
)可以消除数据竞争。

也就是说,在许多常见平台上,写入

int
在硬件级别上将是原子的,编译器不会优化
volatile
访问,也可能不会使用 IO 重新排序易失性访问,因此它可能会起作用在这些平台上。但语言并没有做出这样的保证。

© www.soinside.com 2019 - 2024. All rights reserved.