【问题标题】:Is unprotected access to shared variable always a data race?对共享变量的无保护访问总是数据竞争吗?
【发布时间】:2016-04-17 21:53:47
【问题描述】:

假设 x 是一个共享的线程间变量并且 func 总是返回 0,下面的代码是否包含 C11 和 C++11 方面的数据竞争?请假设 x 是在两个不同的线程中编写的,除了下面的 switch 语句之外,总是带有适当的锁。

int x; // global variable

...

int y; // local variable

...

switch (func())
{
  case 1:
  {
    x = 0;
    y = 1;
    break;
  }
  case 2:
  {
    x = 0;
    y = 2;
    break;
  }
  case 3:
  default:
  {
    y = 3;
    break;
  }
}

标准(C11 和 C++11)中有一条注释排除了编译器转换,该转换会导致代码出现数据竞争。编译器是否允许像下面这样转换代码?下面的代码肯定包含数据竞争,但问题是编译器是否引入了它,或者它是否已经在原始代码中。尽管无法访问,但对共享变量的访问不受保护。

int x; // global variable

...

int y; // local variable

...

temp = x;
x = 0;
switch (func())
{
  case 1:
  {
    y = 1;
    break;
  }
  case 2:
  {
    y = 2;
    break;
  }
  case 3:
  default:
  {
    x = temp;
    y = 3;
    break;
  }
}

【问题讨论】:

  • 在没有任何同步机制的情况下会出现数据竞争,是的。你应该有一个std::atomic<int> x;
  • 这不仅仅是一场数据竞赛。 C 没有并发进程的概念。
  • @mm 您的示例未显示任何并发执行。您能否添加一下,以便为您提供简洁的答案。
  • @πάντα ῥεῖ 它是有意识的。请查看 C11 标准,尤其是章节5.1.2.4 多线程执行和数据竞争,并查看NOTE 13。这就是我的问题的来源。
  • @Yakk:实际上,并没有什么不同。 WG14 和 WG21 在并发方面合作。

标签: c++ c multithreading c++11 c11


【解决方案1】:

在 C++ 标准中,定义了一个竞赛:

1.10/4: 如果其中一个修改内存位置而另一个访问或 修改相同的内存位置。

1.10/21:如果程序的执行包含不同线程中的两个冲突操作,则程序的执行包含数据竞争,至少一个 这不是原子的,也不是在另一个之前发生。任何此类 数据竞争导致未定义的行为。

假设您有多个线程运行相同的代码,由于func() 将始终返回 0(您的声明),因此没有一个线程可以更改 x 的内容。此外,y 是线程执行的函数的局部变量,因此不共享。因此,在这种情况下不会发生竞争条件。

不允许编译器进行第二个sn-p对应的转换,因为:

1.10/22: 编译器转换将分配引入到潜在的共享内存位置,该位置不会被 抽象机器通常被本标准排除在外,因为这样的 一个分配可能会覆盖另一个线程的另一个分配 在没有抽象机器执行的情况下 遇到了数据竞争。

但是如果您自己编写 sn-p,在上述条件下可能会遇到竞速条件,因为 x 不是原子的,并且可以在一个线程 (temp=x) 中进行读取访问,而在另一个线程中进行写入访问(或者x=0 或在其他线程的默认部分 (x=temp)

【讨论】:

  • 感谢您的回答,这是我所期望的,但是我有一点怀疑。我不明白 func 总是返回 0 的事实如何影响 switch 语句中对 x 的赋值与不同线程中对 x 的其他访问之间的 happens-before 关系.
  • @mrn 除了同步提供的线程之外,线程之间没有任何事情发生。
  • @Christophe 然后根据您的说法,数据竞争似乎已经在原始代码中。如果从两个不同的线程访问 x 并且上述 switch 语句中的访问与不同线程中的访问之间没有发生之前的关系,则根据 1.10/21 存在数据竞争。还是您的意思是,如果 func() 始终返回零,则在 switch 语句中根本无法访问 x,因此没有数据竞争,编译器必须考虑这种可能性?
  • 这是一个注释,因此不规范。注释本身不具有约束力,但“本标准通常排除”的措辞暗示标准包含的规则结合起来确保本注释中描述的属性。
  • @Christophe:如果允许编译器为任意变量插入“temp=theVariable; theVariable=0; theVariable=temp;”无论用户代码是否曾经编写过变量,只要感觉像这样,任何多线程的使用都会调用 UB,因为没有什么会阻止一个线程的编译器代码在另一个线程将要使用它之前丢弃一个变量。
猜你喜欢
  • 2012-04-29
  • 1970-01-01
  • 2015-12-03
  • 2014-08-07
  • 2013-10-29
  • 2021-12-21
  • 1970-01-01
  • 2022-06-10
  • 2011-06-07
相关资源
最近更新 更多