多线程
多线程
独立的栈,共享的内存空间
阅读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检查
线程1和线程2同时进入
if块:- 线程1进入
if块并调用usleep(1),暂时让出CPU。 - 线程2也进入
if块并调用usleep(1),暂时让出CPU。
- 线程1进入
线程1和线程2同时修改
balance:- 线程1从
balance中减去 100,balance变为 0。 - 线程2也从
balance中减去 100,balance无符号整数会溢出
- 线程1从
如果是两个进程都循环加1加到10000,结果不会是20000
原代码反汇编
1 | 13ea: 48 8b 05 4f 2c 00 00 mov 0x2c4f(%rip),%rax |
修改成一条汇编指令
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 | load(sum + N) |
O2优化sum=200000000
1 | 0000000000001260 <T_sum>: |
处理器也是编译器,所以单线程的处理器可能会优化,调换程序执行的顺序(在结果不变的情况下)
也是状态机,流水线,读写不冲突就能同时执行
所以……相对论?
共享内存只是一个简化的假象
mem-modle
一个是写Y读X,一个是写X读Y,得按特定顺序才能输出1,1,所以很少
arm与x86的内存模型不同,对于多线程的程序模拟难度大