示例代码#include <iostream> #include <thread> using namespace std; static int g_value = 0; void thread_function() { for (int i=0; i<100000; ++i) g_value++; } int main() { cout << "begin, g_value : " << g_value << endl; std::thread thread_obj_1(thread_function); std::thread thread_obj_2(thread_function); thread_obj_1.join(); thread_obj_2.join(); cout << "end, g_value : " << g_value << endl; return 0; }输出:tiansong@tiansong:~/Desktop$ g++ main.cpp -g tiansong@tiansong:~/Desktop$ ./a.out # 注意每次输出值都不确定 begin, g_value : 0 end, g_value : 113135 tiansong@tiansong:~/Desktop$ ./a.out begin, g_value : 0 end, g_value : 115001 tiansong@tiansong:~/Desktop$ ./a.out begin, g_value : 0 end, g_value : 13696预期的结果应该是 200000, 可实际的结果总是有出入,显然是有问题的。分析 thread_function 对应汇编文件:void thread_function() { 12c9: f3 0f 1e fa endbr64 12cd: 55 push %rbp 12ce: 48 89 e5 mov %rsp,%rbp for (int i=0; i<100000; ++i) 12d1: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 12d8: eb 13 jmp 12ed <_Z15thread_functionv+0x24> g_value++; ## 注意注意,g_value++ 被分解成了三条汇编码 !! 12da: 8b 05 78 2e 00 00 mov 0x2e78(%rip),%eax # 4158 <_ZL7g_value> 12e0: 83 c0 01 add $0x1,%eax 12e3: 89 05 6f 2e 00 00 mov %eax,0x2e6f(%rip) # 4158 <_ZL7g_value> for (int i=0; i<100000; ++i) 12e9: 83 45 fc 01 addl $0x1,-0x4(%rbp) 12ed: 81 7d fc 9f 86 01 00 cmpl $0x1869f,-0x4(%rbp) 12f4: 7e e4 jle 12da <_Z15thread_functionv+0x11> }分析:`g_value++` 被分解成了 3 条汇编码,抽象如下: A,从内存取值到寄存器 B,加一 C,讲加一后的寄存器值放入内存 👇 当线程间没有同步关系(【每个线程有独立栈空间-保留对应各自的寄存器】): 线程 1 执行到 B 步骤(C之前),此时被线程 2 打断,线程 2 调度执行 线程 2 加一的结果放入内存 线程 1 调度执行开始执行,C!! 此处线程 2 的加一被【覆盖】 !!!!!!!!从上面可以看出数据竞争导致的问题在 C/C++ 层面,一条语言往往对应多条汇编语言和机器指令,为了保证完整性,出现了锁用于同步到了 C++11 出现了 atomic 对单个基础数据类型进行封装,原子类型相比 mutex 更轻量拥有更好的性能楼主问题的回答:atomic_load 可以防止出现半成品的中间数值(脏数据)多线程访问同一资源 + 有写有读时,两个条件满足时往往需要考虑锁(atomic)
输出:
预期的结果应该是 200000, 可实际的结果总是有出入,显然是有问题的。
分析
thread_function
对应汇编文件:分析:
从上面可以看出数据竞争导致的问题
在 C/C++ 层面,一条语言往往对应多条汇编语言和机器指令,为了保证完整性,出现了锁用于同步
到了 C++11 出现了 atomic 对单个基础数据类型进行封装,原子类型相比 mutex 更轻量拥有更好的性能
楼主问题的回答: