目录
定义:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
在CPython中,全局解释器锁(GIL),本质是一把互斥锁。用于防止本地线程,多次调用执行Python字节码。
这把锁存在的必要,是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在,其他功能已经发展到依赖于它所执行的保证。
系统层面的线程处理:
- 系统开启多线程
- 线程编译代码,查找语法错误
- 调用解释器外的GIL锁
- 抢到GIL锁的线程,调用解释器执行程序代码
- 结束代码执行,释放GIL锁
未加GIL锁存在的安全隐患(线程安全):
对于同一个数据,线程1的处理是使用数据计算,垃圾回收线程的处理是进行回收。因为进行并发执行,存在恰好,线程1在处理数据前,进行了IO操作导致被垃圾回收线程夺取了解释器执行权,则等线程1再次获取权限执行的时候,数据已经被回收。
原因:1.相同进程内的线程数据共享 2.当CPU处理资源遇到IO操作等会进行权限切换
GIL总结:
- GIL本质是一把互斥锁,相当于使用Cpython解释器的权限,默认每一个进程内都存在一把GIL。
- 同一进程内的多个线程必须抢到GIL后才能使用Cpython解释器执行代码。
- 同一个进程下的多个线程无法实现并行,但是可以实现并发。
- 若想要以线程实现并行,则需要开启多个进程实现。
GIL下的多进程和多线程
CPU单核: ====> 都选择使用线程
多任务处理计算密集型,无法实现并行,创建进程的开销大。选择开启一个进程,多个线程。
多任务处理I/O密集型,无法实现并行的进程开销又大,而切换进程的速度不如线程快。选择开启一个进程,多个线程。
CPU多核:
多任务处理计算密集型,多核实现进程并行,对于线程无法实现并行。选择开启多个进程。
多任务处理I/O密集型,多核情况下进程并发,线程在一个cpu核内(cpu不固定,任意切换)进行切换并发,差别在于并发的切换速度。选择开启一个进程,多个线程。
结论:由于市场上基本上是多核处理器,开发多是IO密集型程序,则对于性能的提升多用多线程。
- 多线程、多进程效率测试
- 计算密集型:多进程效率高
from multiprocessing import Process
from threading import Thread
import os, time
def work():
res = 0
for i in range(100000000):
res *= i
if __name__ == '__main__':
l = []
print(os.cpu_count()) # 查看本地计算器cpu核数 本机为4核
start = time.time()
for i in range(4):
# p = Process(target=work) # 进程处理时间 :run time is 13.770233631134033
p = Thread(target=work) # 线程处理时间:run time is 21.67884659767151
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('run time is %s' % (stop - start))
- I/O密集型:多线程效率高
from multiprocessing import Process
from threading import Thread
import threading
import os, time
def work():
time.sleep(2)
print('===>')
if __name__ == '__main__':
l = []
print(os.cpu_count()) # 本机为4核
start = time.time()
for i in range(400):
p = Process(target=work) # 耗时37.698168992996216多,大部分时间耗费在创建进程上
# p = Thread(target=work) # 耗时 2.1336281299591064
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('run time is %s' % (stop - start))
GIL和Lock(即全局解释器锁和本地锁)
GIL:用来保护解释器级别的数据
Lock:用来保护程序内部的局部数据,实现局部串行。
例:
from threading import Thread, Lock
import time
mutex = Lock()
n = 100
def task():
global n
with mutex:
temp = n
time.sleep(0.1)
n = temp - 1
if __name__ == '__main__':
l = []
for i in range(100):
t = Thread(target=task)
l.append(t)
t.start()
for t in l:
t.join()
print(n)
多线程共同争抢GIL -> 线程1抢到了GIL进入执行代码块 ->线程1开启本地锁,执行代码,遇到IO操作休眠,系统层面被并发 ->线程2抢到GIL锁进入,遇到本地锁被锁定原地等待解放……
打个比喻:
一大门为GIL,是一个人工智能自动门,可检测内部隔间内是否存在浪费资源不干活的人和进入超时的人。
隔间门为Lock本地锁,只能容纳一个人进入,并且进入之后手动上锁。
当一个人进入隔间,偷懒未工作,被AI捕获,则打开大门放一个抢到GIL的新人进来。因为隔间门手动上锁,如果里面人耍流氓不出来,新人也只能原地等待。