GIL:全局解释器锁,只存在于CPython解释器
锁是为了避免资源竞争造成数据的错乱
python程序的执行过程
1.启动解释器进程 :python.exe
2.解析你的py文件并执行它
# 当一个py启动后,会先执行主线程中的代码
# 在以上代码中又开启了子线程,子线程的任务还是执行代码
# 解释器在一个进程中只有一个(解释器也是一堆代码)
# 主线和子线都要去调用解释器的代码,那就产生了竞争关系
为什么要有GIL:在同一时间只有一个线程在使用解释器
每个py程序中都必须有解释器参与,解释器其实就是一堆代码
相当于多个线程要调用同一个解释器代码,共享导致竞争,竞争就要出事,所有给解释器加互斥锁
程序中只有一个线程的时候还需要GIL吗?
python中内存管理依赖于GC(一段用于回收内存的代码)也需要一个线程
除了你自己开的线程,系统还有一些内置线程,就算你的代码不会去竞争解释器,内置线程也可能会竞争所以必须加上锁
例如:GC发现变量x引用计数为0,正准备清扫,CPU突然切换到了另一个线程a
a拿着x进行使用,在使用的过程中,又切换到了GC,GC接着把X指向的空间进行释放
这样一来a中的x就无法使用了,GIL将分配内存回收内存相关的操作加了锁
GIL无法避免自定义的线程中的数据竞争问题
当一个线程遇到了IO,同时解释器也会自动解锁,去执行其他线程,CPU会切换到其他程序
GIL性能:
解释器加锁以后
加锁虽然保证了数据的安全,但是降低了性能,在多CPU的机器上,无法利用多核提升效率
其他线程要想执行,必须等到之前的线程释放了GIL,这就意味着:同一时间只有一个线程在运行,效率比较低
这是一个全球性的问题,你可以尝试解决一下
代码执行有两种状态:
阻塞 遇上i/o,失去CPU的执行权(CPU等待IO完成)
非阻塞 代码正常执行,比如循环一千万次,(CPU占用时间过长)中途CPU可能切换走,又很快会回来(CPU在计算)
假如有32核CPU要处理一个下载任务,电脑性能很高,但是网络速度慢 100k/s,文件大小为1024kb 如果你的代码中IO操作非常多,cpu性能不能直接决定你的任务处理速度
案例: 目前有三个任务,每个任务处理需一秒,获取源数据需要一小时 3个CPU 需要 一小时1秒 1个cpu 需要 一小时3秒
在IO密集的程序中:CPU性能无法直接决定程序的执行速度
在计算密集的程序中:CPU性能可以直接决定程序的执行速度
效率问题致命吗?
不是致命
因为目前很多程序 都是需要网络的 网络速度远比CPU慢 假设你的网络需要10ms python 3ms
区分IO密集 与 计算密集
为什么不用其他解释器?
因为cpython是c语言实现 可以无缝对接c现有的所有库 就是很多现成的功能
多进程:适用于计算密集型,进程属于资源单位,计算会产生大量数据,便于存储
多线程:适用于I/O密集型,线程是CPU的执行单位,相当于流水线,线程切换更加迅速
计算密集测试:
1 from threading import Thread
2 from multiprocessing import Process
3 import time
4
5
6 # 计算密集任务
7
8 def task1():
9 sum = 1
10 for i in range(10000000):
11 sum *= i
12
13
14 def task2():
15 sum = 1
16 for i in range(10000000):
17 sum *= i
18
19
20 def task3():
21 sum = 1
22 for i in range(10000000):
23 sum *= i
24
25
26 def task4():
27 sum = 1
28 for i in range(10000000):
29 sum *= i
30
31
32 def task5():
33 sum = 1
34 for i in range(10000000):
35 sum *= i
36
37
38 def task6():
39 sum = 1
40 for i in range(10000000):
41 sum *= i
42
43
44 if __name__ == '__main__':
45 # 开始时间
46 st_time = time.time()
47 # 多线程情况下
48 # t1 = Thread(target=task1)
49 # t2 = Thread(target=task2)
50 # t3 = Thread(target=task3)
51 # t4 = Thread(target=task4)
52 # t5 = Thread(target=task5)
53 # t6 = Thread(target=task6)
54
55 t1 = Process(target=task1)
56 t2 = Process(target=task2)
57 t3 = Process(target=task3)
58 t4 = Process(target=task4)
59 t5 = Process(target=task5)
60 t6 = Process(target=task6)
61
62 t1.start()
63 t2.start()
64 t3.start()
65 t4.start()
66 t5.start()
67 t6.start()
68 #
69 # t1.join()
70 # t2.join()
71 # t3.join()
72 # t4.join()
73 # t5.join()
74 # t6.join()
75
76 print(time.time() - st_time)
线程和进程在计算密集下性能对比
I/O密集测试:
1 from threading import Thread
2 from multiprocessing import Process
3 import time
4
5 # I/O密集任务
6 def task1():
7 time.sleep(3)
8
9
10 def task2():
11 time.sleep(3)
12
13
14 def task3():
15 time.sleep(3)
16
17
18 def task4():
19 time.sleep(3)
20
21
22 def task5():
23 time.sleep(3)
24
25
26 def task6():
27 time.sleep(3)
28
29 if __name__ == '__main__':
30
31 # 开始时间
32 st_time = time.time()
33 # 多线程情况下
34 # t1 = Thread(target=task1)
35 # t2 = Thread(target=task2)
36 # t3 = Thread(target=task3)
37 # t4 = Thread(target=task4)
38 # t5 = Thread(target=task5)
39 # t6 = Thread(target=task6)
40
41
42 t1 = Process(target=task1)
43 t2 = Process(target=task2)
44 t3 = Process(target=task3)
45 t4 = Process(target=task4)
46 t5 = Process(target=task5)
47 t6 = Process(target=task6)
48
49 t1.start()
50 t2.start()
51 t3.start()
52 t4.start()
53 t5.start()
54 t6.start()
55
56 # t1.join()
57 # t2.join()
58 # t3.join()
59 # t4.join()
60 # t5.join()
61 # t6.join()
62
63 print(time.time() - st_time)
线程和进程在I/O密集下性能对比
与自定义互斥锁的异同:
相同点:都是互斥锁 争抢执行权是无序的 执行被锁定的代码时有序的
不同点:GIL锁的是解释器的数据 自定义互斥锁锁得是用户自定义的数据
GIL的加锁与解锁 是自动执行的
自动释放的时间点: io/代码执行完毕 和 同一线程执行时间过长3ms(py3中)
执行的字节码指令数量达到一定值(py2中)
代码:
1 from threading import Thread, Lock
2 import time
3
4 mutex = Lock()
5 num = 1
6
7 def task():
8 global num
9 mutex.acquire()# 第二个线程被互斥锁阻塞,等待线程一执行结束
10 temp = num
11 # print(temp)
12 time.sleep(1) # 当你们线程中出现io时,GIL锁就解开,去执行第二个线程
13 num = temp + 1
14 mutex.release() # 线程任务结束时GIL锁解开
15
16 t1 = Thread(target=task, )
17
18 t2 = Thread(target=task, )
19 t1.start()
20 t2.start()
21 t1.join()
22 t2.join()
23 print(num) # 执行结果为3