[Python/多线程/并发] Python底层运行机制:全局解释器锁(GIL)
0 序
- 缘起于 Python异步编程关键字: Python 关键字辨析:async / await / yield / assert - 博客园/千千寰宇
1 概述:Python全局解释器锁(GIL)
GIL的定义



Python全局解释器锁(GIL, Global Interpreter Lock)是一个全局级的互斥锁,它确保在任何时候只有一个线程可以控制Python解释器。这意味着:即使在多核处理器的【多线程架构】中,也只有一个线程可以在任何时候执行。

对于执行【单线程程序】的开发者来说,GIL的影响并不明显,但它可能成为【CPU密集型任务】和【多线程代码任务】的性能瓶颈。

GIL的工作原理
GIL的存在是为了防止多线程同时增加或减少对象的引用计数,这可能会导致内存泄漏或错误释放内存,从而引发崩溃或其他异常错误。
GIL通过在【解释器级别】添加一个【规则】来解决这个问题,即:执行任何Python字节码都需要获取【解释器锁】。
这避免了【死锁】(因为只有一个锁),并且没有引入太多的【性能开销】。
然而,这实际上使得任何CPU密集型的Python程序都变成了【单线程】。
GIL的影响:不利于CPU密集型程序(多线程程序)
- GIL对【多线程Python程序】的影响取决于程序是【CPU密集型】还是【I/O密集型】。
- 对于【CPU密集型程序】,GIL阻止了线程在多核处理器上并行执行。
- 对于【I/O密集型程序】,由于线程在等待I/O时【会释放GIL】,因此GIL的【影响相对较小】。
GIL的存在使得在多核CPU上的Python【多线程程序】无法充分利用【多核性能】。
如何处理GIL的限制?
- 面对GIL的限制,开发者可以采取以下几种方法:
- 使用多进程:通过使用
multiprocessing模块,可以创建多个【进程】来充分利用【多核CPU】。每个进程都有自己的【Python解释器】和【GIL】——因此,能够避免GIL对【多线程程序性能的影响】。参见本文档: 多进程Python Demo 章节。
- 使用C扩展:编写关键部分的代码为C扩展,因为C语言中不存在GIL的概念。使用C编写Python的关键性能部分(如CPU密集型计算),可以规避GIL的限制。
- 异步编程:采用【异步编程模型】,如
asyncio库,可以在【I/O密集型任务】中充分利用【事件循环】和【协程】来避免GIL的影响。【异步编程】避免了【线程的阻塞等待】,使得【单个线程】能够处理多个任务。
- 使用其他Python解释器:一些Python的替代解释器,如
Jython、IronPython等,不同程度上【规避】了GIL的存在,这些解释器可能更适合某些特定的应用场景。
- 使用线程池:使用
concurrent.futures模块中的【线程池】可以减少【线程创建】和【销毁的开销】,【有效管理线程】,并能部分规避GIL的限制。
Python多进程机制(multiprocessing)
注意:Windows 下使用
multiprocessing,【进程入口代码】必须放在if __name__ == '__main__'保护块里面,否则会无限递归创建【子进程】。
- Windows 必须
if __name__ == '__main__',Linux/macOS 可以不用,但建议统一写上,保证跨平台兼容。 start():启动进程;join():阻塞等待进程结束。- 进程之间【内存隔离】,【全局变量】无法【直接共享】,通信使用
Queue/Pipe/Manager。 - 【CPU 密集场景】适合多进程;【IO 密集】优先考虑多线程
threading或者asyncio。 Pool.map会把【任务序列】分发给进程池,返回结果列表。
Demo1:基础多进程
import multiprocessing
import time
def worker(name: str, delay: int):
"""子进程执行的任务"""
print(f"子进程 {name} 启动, pid={multiprocessing.current_process().pid}")
time.sleep(delay)
print(f"子进程 {name} 执行完毕")
if __name__ == '__main__':
print(f"主进程 pid={multiprocessing.current_process().pid}")
# 创建2个子进程
p1 = multiprocessing.Process(target=worker, args=("进程A", 2))
p2 = multiprocessing.Process(target=worker, args=("进程B", 1))
# 启动进程
p1.start()
p2.start()
# 等待子进程结束,主进程再往下执行
p1.join()
p2.join()
print("所有子进程执行完成,主进程退出")
- 输出示例
主进程 pid=1234
子进程 进程A 启动, pid=5678
子进程 进程B 启动, pid=9012
子进程 进程B 执行完毕
子进程 进程A 执行完毕
所有子进程执行完成,主进程退出
Demo2:进程池 Pool(批量任务,更常用)
- 适合大量任务,控制并发数量
import multiprocessing
import time
def task(x):
print(f"处理任务 {x}, pid={multiprocessing.current_process().pid}")
time.sleep(1)
return x * x
if __name__ == '__main__':
# 进程池最大并发数3
with multiprocessing.Pool(processes=3) as pool:
# map: 自动分配任务
results = pool.map(task, range(5))
print(f"任务结果:{results}")
Demo3:进程之间通信 Queue
- 多进程不能直接共享变量,用 Queue 传递数据
import multiprocessing
def producer(q: multiprocessing.Queue):
"""生产者:往队列放数据"""
for i in range(3):
q.put(i)
print(f"生产:{i}")
def consumer(q: multiprocessing.Queue):
"""消费者:从队列取数据"""
while not q.empty():
val = q.get()
print(f"消费:{val}")
if __name__ == '__main__':
queue = multiprocessing.Queue()
p_pro = multiprocessing.Process(target=producer, args=(queue,))
p_con = multiprocessing.Process(target=consumer, args=(queue,))
p_pro.start()
p_pro.join()
p_con.start()
p_con.join()
结论
- 虽然GIL是Python的一个挑战,但通过合理的技术选择和编程实践,我们能够最大程度地发挥Python的优势,并充分利用多核CPU,提高程序的效率和性能。
了解GIL的工作原理和其对多线程程序的影响,有助于我们更好地理解Python的并发模型,并选择合适的解决方案来优化程序性能。
随着技术的发展和Python生态系统的进步,我们相信对于GIL的限制也会有更多的解决方案和改进,为Python程序的并发性能提供更多可能性。
Y 推荐文献
X 参考文献
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号