GIL 全称 Global Interpreter Lock,意为全局解释器锁。在 Python 中,一次只能执行一个线程,所以为了保证线程安全,引入了 GIL 的概念。GIL 是 Python 解释器中自带的机制,用来保证同一时刻只有一个线程在执行 Python 代码。当一个线程开始执行 Python 代码时,GIL 就会获得并锁住 Python 解释器,保证只有这个线程可以接触到解释器。
GIL 的存在使得 Python 的多线程执行遭遇瓶颈。虽然 Python 可以使用多进程的方式解决多线程所面临的瓶颈,但多进程的开销相比于多线程会比较大。而且在某些场景下,使用多线程更为方便。
import threading
num = 0
def thread_test():
global num
for i in range(1000000):
num += 1
print(num)
thread1 = threading.Thread(target=thread_test)
thread2 = threading.Thread(target=thread_test)
thread1.start()
thread2.start()
以上代码会创建两个线程,每个线程都会对 num
进行 1000000 次加 1 操作,并打印最终的计数结果。
但是执行的结果可能出乎想象:
1265211
1394415
实际上,我们想要的结果应该是 2000000。原因就在于 GIL。
在使用多线程执行 Python 代码时,由于 GIL 的存在,每一个时刻只有一个线程在执行 Python 代码。在本例中,两个线程交替获得 GIL,一次只能做一次加法。最终的结果可能超出预期。
import threading
num = 0
def thread_test():
global num
for i in range(1000000):
num += 1
print(num)
thread1 = threading.Thread(target=thread_test)
thread2 = threading.Thread(target=thread_test)
thread3 = threading.Thread(target=thread_test)
thread1.start()
thread2.start()
thread3.start()
以上代码会创建三个线程,每个线程都会对 num
进行 1000000 次加 1 操作,并打印最终的计数结果。
执行的结果可能如下:
2324507
2424009
3553736
同样也是超出预期的。
在这个例子中,如果想要结果正确,可以使用 threading.Lock()
对共享变量进行锁定:
import threading
num = 0
lock = threading.Lock()
def thread_test():
global num
for i in range(1000000):
lock.acquire()
num += 1
lock.release()
print(num)
thread1 = threading.Thread(target=thread_test)
thread2 = threading.Thread(target=thread_test)
thread3 = threading.Thread(target=thread_test)
thread1.start()
thread2.start()
thread3.start()
使用 lock
后,每个线程在执行对 num
的加 1 操作前都会尝试获取锁,如果拿到锁后才执行加法操作。这样可以保证在同一时刻只有一个线程对 num
进行操作,最终达到正确的结果。