GIL
一、GIL介绍
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的特性,它是在实现Python解析器(CPython)时所引入的一个概念。
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
二、为什么需要GIL
再python的进程中,除了主线程还有其他线程,包括python解释器开启的垃圾回收等解释器级别的线程。假如代码n=1执行时,定义变量n的同时垃圾回收的线程恰好发现变量n并没有绑定值而直接回收。此时的情况是不能允许的,所以需要锁让同一时刻只能有一个线程操作数据,这便是GIL。
三、检验GIL存在
如下代码检验GIL的存在,定义m=100,创建100个线程同时修改m。
from threading import Thread
import time
m = 100
def test():
global m
tmp = m
tmp -= 1
m = tmp
for i in range(100):
t = Thread(target=test)
t.start()
time.sleep(3)
print(m) # 0
可能你会觉得m是99,100个线程获得的m都是100,各自-1赋值m为99。但是由于GIL的存在,同一个进程中的多个线程操作数据时是串行的,即获取GIL锁,修改数据并释放GIL锁。
python多线程
介绍完GIL是否会有疑问,同一时刻只有一个线程操作数据,python多线程开销小,但是无法利用多核优势,python狗都不用?
解决这个疑惑,我们需要知道几个点:
- cpu遇到io操作会切换,多核对于io操作来说毫无作用。
- 计算任务会持续持有cpu,多核意味着并行完成计算,提高计算效率。
所以,对于计算密集型任务,多进程(多核)能够提升执行效率;IO密集型任务,多线程执行效率高。
实验:
1、计算密集型任务
from threading import Thread
from multiprocessing import Process
import time
def task():
ret = 1
for i in range(10000000):
ret *= i
return ret
if __name__ == '__main__':
l = []
start = time.time()
for i in range(16):
p = Process(target=task) # 用时0.9139392375946045秒
# t = Thread(target=task) # 用时4.870624780654907秒
l.append(p)
# l.append(t)
p.start()
# t.start()
for j in l:
j.join()
end = time.time()
print(end-start)
2、IO密集型任务
from threading import Thread
from multiprocessing import Process
import time
def task():
time.sleep(2)
if __name__ == '__main__':
l = []
start = time.time()
for i in range(100):
p = Process(target=task) # 用时7.828985214233398秒
# t = Thread(target=task) # 用时2.0460045337677秒
l.append(p)
# l.append(t)
p.start()
# t.start()
for j in l:
j.join()
end = time.time()
print(end-start)