在 C++ 中增加变量值时的竞争条件

Race condition when increase a variable value in c++

我最近在一次采访中被问到这个问题。问题是我们是否有一个函数将 int i 的引用作为参数并且只执行 i++。没有任何线程同步。现在在 main 函数中,我们用 0 初始化变量 i,然后我们创建 7 个新线程到 运行 相同的函数并传递相同的变量 i,在所有线程之后加入后,变量 i 有多少个可能的值?非常感谢!

我试着思考 i++ 的说明。像 loadaddstore 指令。假设我们使用 g++ 编译器并且在 Linux OS.

您描述的代码有未定义的行为。 i 的值未定义。如果你确实编译并 运行 代码并在屏幕上打印 i 输出可能是任何东西。可以是 42"hello world".

也许这个问题是为了让您考虑有多少种不同的方法可以让多个线程递增 i 以及这会如何影响输出。但是,当代码有未定义的行为时,逻辑推理是徒劳的。

另一方面,如果您关心编译代码中实际发生了什么,那么了解这一点的唯一方法就是查看编译器生成的程序集。结果程序做什么是不确定的,但它确实做了一些事情。虽然你只有在编译它并研究汇编之后才能知道那是什么。 C++ 标准没有描述这样的程序将做什么,而且你得到的任何东西都是不可移植的。

其他回答中提到有UB

此外,可能会出现此数字根本不增加或增加几次(小于 7)或其他任何情况。因为它不是一个原子变量(问题没有提到) - 它会产生非常 unstable/unpredictable 的结果。 See/google 用于“内存模型”以获得更多解释。

用于递增变量的具体指令取决于指令集,但除非使用某种原子递增,否则它将分解为您所描述的 load/add/store 操作。在 x86 上,这可能通过单个 inc 指令完成,但如果不锁定,它仍将分解为内部 load/add/store 操作。

您将要查看这些序列的可能交错。交织是由 non-atomic 序列的中断引起的。这是一种可能的交错:

thread 1      thread 2
 load   
               load
               add
               store
 add
 store

该序列将使变量仅由线程 1 递增一次,因为线程 2 的递增操作实际上被忽略了 — 线程 1 存储最后一个,因此“获胜”。 (从某种意义上说,线程 2 以错误的值开始,因此线程 2 的存储与线程 1 的存储具有相同的值 (+1)。)

因此,在一个极端情况下,答案是变量只会增加 1。在另一个极端,如果每个线程在不中断该序列的情况下成功递增,则变量将递增 7 次。所有中间值(增加 2-6)也是可能的。


由于根本没有同步,我们还必须考虑在连接后我们观察到原始 0 的可能性,尽管我认为这不太可能,因为涉及的系统调用是自然同步的在创建线程并加入它们时。