11.1 Global Interpreter Lock
Global Interpreter Lock (GIL)
是一种在
CPython 解释器中确保一次只有一个线程能够执行 Python 字节码的机制。
GIL
阻止了线程的并行执行,这可能对多线程程序的性能产生负面影响,
特别是在多核处理器上。
GIL
存在的原因
简化内存管理:GIL
简化了内存管理和垃圾回收,使得 Python 实现更加简单。
线程安全:GIL
防止了竞态条件,使得代码在不使用锁定的情况下是线程安全的。
GIL
引发的问题
性能限制:多线程程序在执行计算密集型任务时,不能完全利用多核处理器的优势,因为 GIL
的限制。
性能失真:密集使用线程执行任务的程序可能会由于线程间的上下文切换而导致性能下降。
现在有四种主要的方法来“绕过 GIL”:
11.2 使用多进程 (multiprocessing)
multiprocessing
模块 允许创建并行执行的进程,不受 GIL
的限制,
因为每个进程都有自己的 Python 解释器和内存。
示例:
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 异步编程
使用 asyncio
的异步编程允许任务并行执行,而不阻塞主线程。
虽然这在字面意义上并不绕过 GIL
,但它允许有效地利用等待时间来执行其他任务。
示例:
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 使用自己管理线程的库
一些库,比如 NumPy 和 SciPy,是用 C 编写的,并使用自己的线程管理机制,从而绕过 GIL,能在多核处理器上高效地进行计算。
事实上,这也是 Python 成功的主要原因之一,无论它本身有多慢。所有复杂的计算都用 C/C++ 语言重写,运行在所有处理器核心上,甚至可以直接在显卡核心上运行。在现代显卡上有成千上万个核心。
所以,现在语言本身有多快或多慢已经不重要了,只要所有消耗资源的计算都是通过外部库或在远程数据中心完成的。
11.5 在 Python 解释器外执行计算
使用 C/C++ 或其他语言的扩展模块,它们可以在 Python 解释器外进行计算,然后返回结果。这样可以避免在执行密集运算时被 GIL 锁住。
使用 Cython 的示例:
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
编译和使用:
cythonize -i example.pyx
import example
print(example.compute(1000000))
GO TO FULL VERSION