多线程
独立的栈,共享的内存空间
阅读thread.h源码
在thread-qa中
将thread.h移入文件夹,执行make报错找不到库
解决方法:设置TLIB_PATH路径为thread.h所在的文件夹
1 CFLAGS := -O1 -g -I$(TLIB_PATH)
解释
-I$(TLIB_PATH):
这个选项指定了一个包含路径,该路径由 $(TLIB_PATH) 变量定义。
$(TLIB_PATH) 可能在 Makefile 的其他地方定义,或者在运行 make 命令时通过环境变量传递。
编译器会在 $(TLIB_PATH) 指定的目录中查找头文件。
-I.:
这个选项指定当前目录(.)作为包含路径。
编译器会在当前目录中查找头文件。
多个进程读取写入
支付宝,第一个减去100,第二个在该进程没有减去的时候进行条件判断,也减去100,由于是unsigned long 结果变成很大的数
copilot解释
竞态条件的发生
共享变量 :balance 是一个全局变量,多个线程可以同时访问和修改它。
线程创建 :main 函数中创建了两个线程,分别执行 T_alipay 函数。
函数调用 :每个线程调用 Alipay_withdraw(100),尝试从 balance 中扣除 100。
竞态条件的具体过程
线程1和线程2同时检查 balance :
线程1检查 balance 是否大于等于 100,结果为真。
线程2也检查 balance 是否大于等于 100,结果也为真。
线程1和线程2同时进入 if 块 :
线程1进入 if 块并调用 usleep(1),暂时让出CPU。
线程2也进入 if 块并调用 usleep(1),暂时让出CPU。
线程1和线程2同时修改 balance :
线程1从 balance 中减去 100,balance 变为 0。
线程2也从 balance 中减去 100,balance 无符号整数会溢出
如果是两个进程都循环加1加到10000,结果不会是20000
原代码反汇编
1 2 3 4 13ea: 48 8b 05 4f 2c 00 00 mov 0x2c4f(%rip),%rax # 4040 <sum> 13f1: 48 83 c0 01 add $0x1,%rax 13f5: 48 89 05 44 2c 00 00 mov %rax,0x2c44(%rip)
修改成一条汇编指令
1 13ea: 48 ff 05 4f 2c 00 00 incq 0x2c4f(%rip)
改成一条指令,如果在一个处理器上还能正确,但是在多处理器上还会错误。
看着是一条指令,实际上不是原子指令。
printf是线程安全的
因为汇编指令取值,在中间寄存器加1后可能会中断,再放入变量对应地址中,结果可能就只是加一个1,而不是两个1
最小可以小于10000,可以改成汇编指令
为什么是2?
每个进程有一个n次循环,n个进程
在关键进程中,最后一步store,之前,已经循环了n-1次了,这两次的最小值为1(进程A第一个循环store()时,关键进程第n-1个循环正好结束,进程A,store后sum=1)关键进程执行前两步,然后关键进程等待其他进程结束后执行store(2,sum)
编译器优化,可能会隐藏并发的bug(都假设状态迁移是原子性,顺序执行)
O1优化sum=100000000
1 2 3 load(sum + N) ;循环N次 store(num)
O2优化sum=200000000
1 2 3 0000000000001260 <T_sum>: 1264: 48 81 05 d1 2d 00 00 addq $0x5f5e100,0x2dd1(%rip) ;改成了一条指令,两个进程碰在一起的概率很低
处理器也是编译器,所以单线程的处理器可能会优化,调换程序执行的顺序(在结果不变的情况下)
也是状态机,流水线,读写不冲突就能同时执行
所以……相对论?
共享内存只是一个简化的假象
mem-modle
一个是写Y读X,一个是写X读Y,得按特定顺序才能输出1,1,所以很少
arm与x86的内存模型不同,对于多线程的程序模拟难度大