一、什么是GIL锁?
GIL全称Global Interpreter Lock(全局解释器锁),是一种管理线程同步的互斥锁,Python因其解释型语言的特性,需要借此来保证每个Python线程在任意时刻有且仅有一个线程在执行。它是Python解释器设计的核心,在CPython中实现。
GIL并不是Python的语言特性,而是CPython的实现特性,其他Python解释器如Jython、IronPython等并不需要GIL。
二、GIL锁对Python多线程的影响
GIL锁会导致单核环境下,并发编程的性能反而不如串行化编程。因为Python多线程在GIL的限制下,存在不能真正并行处理的问题。但在多核环境下,由于不同核心的线程可以互不干扰地进行并行处理,因此可以得到一定的性能提升。
还有一点需要注意,由于GIL锁的存在,Python多线程在CPU密集型任务中并不能发挥出并发编程的优势,但在IO密集型任务中,通过Python异步编程技术可以降低阻塞等待的时间,提升并发性能。
三、GIL锁的影响因素
通常情况下,GIL的影响因素有以下几个方面:
1. GIL锁的获取和释放
import threading
import time
def task():
print("start task...")
time.sleep(1)
print("end task...")
def count():
print("start count...")
for i in range(10**6):
pass
print("end count...")
def run_tasks():
threads = []
thread1 = threading.Thread(target=task)
threads.append(thread1)
thread2 = threading.Thread(target=task)
threads.append(thread2)
thread3 = threading.Thread(target=count)
threads.append(thread3)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
run_tasks()
上述代码中,我们定义了三个线程,其中两个线程执行的是一个比较短的任务(打印“start task...”和“end task...”之间的时间间隔为1秒),另一个线程执行的是一个比较耗时的任务(运行10^6次循环)。通过执行结果可以看出,由于GIL锁的存在,三个线程并不能完全并行,而是依次执行的。
2. Python执行的代码类型
GIL锁并不会影响所执行的系统级代码,诸如C、C++编写的库、部分NumPy计算库等不受其限制。因此,Python在使用大量C、C++的模块时,会形成另外的线程运行C语言代 码,从而绕过GIL的限制。
3. 线程I/O等待时间
GIL锁在执行I/O操作的线程等待内核(I/O等待),或在锁等待状态下,会释放锁,给其他线程执行机会。
四、如何避免GIL锁的影响?
虽然GIL锁存在,但我们仍然可以在Python多线程编程中得到优秀的性能。以下是几种避免GIL锁的影响的方法:
1. 使用多进程编程
Python的多进程编程可以很好地避免GIL锁的影响。但是和线程相比,进程由于需要独立的内存空间和上下文切换的开销较大,因此在一些场景下会存在性能瓶颈。
2. 使用Cython
Cython是一种Python的编译器,它可以将Python代码转化为C语言的扩展模块。这个模块可以绕过GIL的内存枷锁机制,在多线程情况下获得更好的性能。因此,Cython适合处理CPU密集型操作。
3. 使用Jython、IronPython等Python解释器
由于GIL锁只存在与CPython中,因此在Jython、IronPython等Python解释器中是不存在GIL锁的。
4. 使用协程(Coroutine)
协程是相对于线程、进程而言的第三种并发方案,是一种轻量级的并发技术,有着比线程更低的创建和上下文切换开销。Python中常用的协程方案主要包括生成器(generator)、asyncio等。在使用协程时,需要注意全局解释器锁中与IO等待的影响。
五、总结
GIL是Python多线程编程中的一个瓶颈,它限制了Python多线程并发性能。但是在实际中,我们并不需要一定要离开Python有很好的性能。我们可以通过多进程编程、使用Cython、使用Jython、IronPython等Python解释器、使用协程等方法来有效避免GIL锁的影响。