【问题标题】:Does this example contain a data race?此示例是否包含数据竞争?
【发布时间】:2018-01-05 07:45:32
【问题描述】:

这是原始问题,但我的问题与它有一些不同。 C++ memory model - does this example contain a data race?

我的问题:

//CODE-1: initially, x == 0 and y == 0
if (x) y++; // pthread 1
if (y) x++; // pthread 2

注意:上面的代码是用 C 编写的,而不是 C++(没有内存模型)。那么它是否包含数据竞争?

在我看来:如果我们在顺序一致性内存模型中查看代码,则不会存在数据竞争,因为 x 和 y 永远不会同时为非零。但是,我们永远不能假设一个顺序一致性内存模型,因此编译器重新排序可以对线程内正确性进行转换,因为编译器不知道线程的存在......。对吗?

所以代码可以转换为:

//CODE-2
y++; if (!x) y--;
x++; if (!y) x--;

上面的转换并没有违反顺序正确性所以是正确的。这不是编译器的错,对吧?所以我同意 CODE-1 包含数据竞争的观点。你呢?

我有一个额外的问题,带有内存模型的 C++11 可以解决这种数据竞争,因为编译器知道线程,所以他们会根据内存模型类型重新排序,对吧?

【问题讨论】:

  • 在多线程环境中对共享资源的访问应该受到保护。这可以通过使用互斥锁来完成。如果x 和/或y 未在线程(函数)本地定义,则x 和/或y 是共享资源并且存在访问它们的竞争。
  • 是的,除非两个线程同步它们对xy 的访问,否则存在竞争条件。而 C++11 更新的内存模型并没有改变这一点。
  • @Peter Thx,所以你的意思是用 C 编写的 CODE-1 包含数据竞争?
  • @alk 谢谢。所以我的理解是对的?
  • 如果编译器可以无中生有地发明写入,那么任何事情都可能是不正当的,在这样的实现中对竞争进行推理是没有意义的。无论您使用哪种同步,编译器都可以在受保护的块之外发明对共享资源的写入,然后繁荣,竞争。

标签: c multithreading c++11 java-memory-model memory-model


【解决方案1】:

C++ 标准将数据竞争(触发未定义行为)定义为:

§ 1.10.1-2 [intro.races]
如果其中一个修改了内存位置 (..) 而另一个读取或修改了相同的内存位置,则两个表达式求值会发生冲突。

根据 C++ 内存模型规则,您的第一个代码片段不包含数据竞争,因为 C++ 标准禁止编译器转换会引入这种竞争:

§ 1.10.1-21 [intro.races]
本国际标准通常排除将分配引入抽象机不会修改的潜在共享内存位置的编译器转换,因为在抽象机执行不会执行的情况下,这样的分配可能会覆盖不同线程的另一个分配遇到了数据竞争。

所以它表示如果 if 语句 (x) 中的条件为 false,则不允许任何会修改 y 的转换,即使最终结果是 y 似乎未修改。

第二个示例显然包含数据竞争,因为 2 个线程可以同时写入和读取 x(同样适用于 y)。

请注意,C++ 和 C 从版本 11 开始都有内存模型。如果您使用不支持 C11 的编译器,则未正式定义多线程行为。

这是一个question,它显示了一个非法编译器转换的示例。

【讨论】:

  • 谢谢。 “根据 C++ 内存模型规则,第一个代码片段中没有数据竞争(尽管它与顺序一致的内存模型无关)。”你的意思是C++不会有数据竞争,和顺序一致的内存模型没有关系?我不太明白你的意思。而且,在这个问题中,pre-C11 C 和 C11 C 之间的关键点是什么?我对此一无所知。
  • @LiuShenming 我删除了关于 seq/cst 内存模型的评论,因为它并没有真正添加任何有用的东西
  • 我想补充一些对许多人来说似乎不清楚的评论:数据竞争是未定义的行为。 [intro.races] 中提到的竞赛是指非原子变量。使用原子变量时不会有竞争。当然,根据内核调度,有时一个线程运行得更快,有时另一个线程运行得更快,因此程序的输出因运行而异,但这不是“数据竞争”,也不是 UB。因此,如果在给定的代码中 sn-p x 或 y 是非原子的,我们就有一个数据竞争(假设没有发生之前的关系,即使用 a
  • 给定代码周围的互斥体)。如果 x 和 y 是原子的,那么我们就不是。通常,给定的代码用于询问“执行结束时 x 和 y 的结果是否为 1?”的问题?不过,这个问题与种族无关。给定的代码使用了对 x 和 y 的默认顺序一致的内存访问,这使得推理几乎是微不足道的:CODE-1 将始终导致 0、0。绝不允许编译器更改 CODE-2,因为 x 和y 是原子的,这完全改变了 x 和 y 的操作方式。请注意,如果 if() 条件中的读取是放松的
  • 那么最后的结果 x==1 和 y==1确实成为可能。
【解决方案2】:

//CODE-1:最初,x == 0 和 y == 0
如果 (x) y++; // pthread 1
如果 (y) x++; // pthread 2

没有未定义的行为,因为 x 和 y 都不会改变它们的值。
但是,仍然存在竞争条件,因为在一个线程中的读取访问和另一个线程中的写入访问之间没有定义的顺序。

//CODE-2
是++;如果 (!x) y--; // pthread 1
x++;如果 (!y) x--; // pthread 2

现在你有一个数据竞争和未定义的行为,因为线程 1 中的 y++ 和线程 2 中的 if(!y) 之间没有序列,反之亦然。所以 y 的可能结果是:

  • y = 0
    线程 1 在线程 2 之后运行。所以 x 仍然是 0。
  • y = 1
    线程 1 与线程 2 并行运行,看到对 x 的更改,反之则不然。所以 y 没有递减。

这与内存模型无关。这只是在任何不同步的情况下进行的比赛。

【讨论】:

  • 竞争条件的定义,在这种情况下,是一个线程对xy 的任何访问都可以被同样访问x 或的另一个线程中断或抢占。 y。即使代码的最终效果是没有任何变化,也存在竞争条件。
  • @Peter,你是对的,在关系之前没有定义发生。我会改变措辞。
  • @Marcel 但是如果没有语言级别指定的内存模型,编译器将进行重新排序,从而引入数据竞争,因此程序员无法对他们的代码何时包含数据竞争做出任何假设(例如 CODE-1 )。所以我认为它与内存模型有关,C没有内存模型,因此程序员和编译器对于CODE-1是否包含CODE-1中的数据竞争没有共识。跨度>
  • @Marcel 您关于第二个代码片段的 cmets 不正确。这不是竞争条件,而是对共享数据的访问冲突,即数据竞争。它不会产生未定义的结果,但未定义的行为意味着线程如何交错的推理不适用。
  • 由于大家似乎没有说“没有数据竞争期,因为 x 和 y 是原子的”,我不得不假设 x 和 y 是非原子的。在这种情况下,此代码确实存在数据竞争并且是未定义的行为周期。这里有 if 语句真的没关系(对于内存模型的抽象机器):/.
猜你喜欢
  • 1970-01-01
  • 2018-11-25
  • 1970-01-01
  • 2016-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
相关资源
最近更新 更多