多线程

多线程

独立的栈,共享的内存空间

阅读thread.h源码

在thread-qa中

将thread.h移入文件夹,执行make报错找不到库

解决方法:设置TLIB_PATH路径为thread.h所在的文件夹

1
CFLAGS := -O1 -g -I$(TLIB_PATH)

解释

  1. -I$(TLIB_PATH)
    • 这个选项指定了一个包含路径,该路径由 $(TLIB_PATH) 变量定义。
    • $(TLIB_PATH) 可能在 Makefile 的其他地方定义,或者在运行 make 命令时通过环境变量传递。
    • 编译器会在 $(TLIB_PATH) 指定的目录中查找头文件。
  2. -I.
    • 这个选项指定当前目录(.)作为包含路径。
    • 编译器会在当前目录中查找头文件。

多个进程读取写入

支付宝,第一个减去100,第二个在该进程没有减去的时候进行条件判断,也减去100,由于是unsigned long 结果变成很大的数

copilot解释

竞态条件的发生

  1. 共享变量balance 是一个全局变量,多个线程可以同时访问和修改它。
  2. 线程创建main 函数中创建了两个线程,分别执行 T_alipay 函数。
  3. 函数调用:每个线程调用 Alipay_withdraw(100),尝试从 balance 中扣除 100。

竞态条件的具体过程

  1. 线程1和线程2同时检查 balance

    • 线程1检查 balance 是否大于等于 100,结果为真。
    • 线程2也检查 balance 是否大于等于 100,结果也为真。
  2. 线程1和线程2同时进入 if

    • 线程1进入 if 块并调用 usleep(1),暂时让出CPU。
    • 线程2也进入 if 块并调用 usleep(1),暂时让出CPU。
  3. 线程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的内存模型不同,对于多线程的程序模拟难度大