concurrency - 如何理解两个线程递增和打印一个全局变量的结果?
问题描述
在我的主线程中,我有两个线程运行以下函数:
int i = 0;
void foo()
{
++i;
printf("%d", i);
}
我运行了 10000 次,得到了 3 个不同的结果:{1 2}、{1 1}、{2 1}
前两个我明白。问题是结果如何是 {2 1} 以及为什么 {2 2} 根本不出现。
谢谢!
解决方案
要了解为什么任何特定的未定义行为实例碰巧在特定机器上以特定方式执行,您通常必须阅读编译器生成的汇编代码。 这是 x86-64 上的 gcc 10.2 产生的内容。它大致执行以下操作:
- 从内存加载
i
到寄存器 - 增加寄存器
- 将寄存器存储回
i
- 将寄存器的内容传递给
printf
.
所以想象一下线程碰巧按以下顺序执行这些步骤:
Thread A Thread B
-------- --------
Step 1
Step 2
Step 3
Step 1
Step 2
Step 3
Step 4
Step 4
然后你可以清楚地看到输出将是2 1
.
至于为什么2 2
永远不会发生,请注意要打印的值是在第 1 步加载的。两个线程之一必须先于另一个线程执行第 1 步,或者同时执行。因此,其中至少有一个将0
在第 1 步加载,因此将打印1
. 即使另一个线程碰巧i
在第一个线程打印它的值之前更新了内存中的值,它也不会有任何区别,因为要打印的值已经在第一个线程的寄存器中。
(当然,其他一些编译器完全有可能生成i
从内存重新加载的代码,在这种情况下2 2
可能会发生。对于这个特定的生成代码,情况并非如此。)
推荐阅读
- azure - Azure DevOps 版本 - terraform 导入失败并显示“使用服务主体进行身份验证”
- windows - 计划任务 - 针对特定触发器的特定操作
- python - 将 Python 装饰器与来自全局变量的数据一起使用
- c - 在子程序中使用/不使用 return 有什么区别?
- r - 在 dplyr mutate 中取领先和滞后日期的平均值
- sql - 在 Join (SQL) 中使用替换函数
- sql-server - 防止按 NULL 值对行进行分组
- typescript - 开玩笑 - spyOn 没有代码覆盖实际实现
- python - Keras 错误:图形无法按拓扑顺序排序
- javascript - Hybris 1811 SmartEdit Loader 一直在加载