Python GIL 对多线程与多进程的影响及优化策略

橘子海
橘子海
发布时间:2026-10-14 | 5人浏览过
原创

本文深入解析Python全局解释器锁(GIL)机制,阐述其对多线程执行效率的限制。针对CPU密集型与IO密集型任务,对比分析多线程与多进程的适用场景,并结合代码实例说明如何在Python中合理选择并发模型以充分利用多核CPU性能。

Python GIL机制概述

Python全局锁

(1)全局锁导致的问题

Python的全局解释器锁(Global Interpreter Lock,简称GIL)源于早期设计时对数据安全的考量。其核心机制要求每个线程在执行前必须获取GIL,确保同一时刻仅有一个线程执行代码,即同一时刻只有一个线程占用CPU。因此,Python中的多线程并非真正意义上的并行执行。 在单核CPU环境下,多线程表现为并发而非并行。宏观上,并发与并行均涉及多路请求的处理,但二者存在本质区别:并行指多个事件在同一时刻发生(例如多个CPU同时执行任务),而并发指多个事件在同一时间间隔内交错发生。

在Python多线程环境下,每个线程的执行流程如下:

1、获取GIL

2、执行代码,直到遇到sleep操作或Python虚拟机主动挂起该线程。

3、释放GIL

由此可见,线程执行的前提是获取GIL。可将GIL视为“通行证”,且在一个Python进程中仅存在一个GIL。无法获取通行证的线程禁止进入CPU执行。

在Python 2.x版本中,GIL的释放逻辑基于两种情况:线程遇到IO操作,或Python虚拟机的ticks计数达到100(ticks是Python内部用于控制GIL的计数器,每次释放后归零,可通过 sys.setcheckinterval 调整阈值)。 每次释放GIL锁时,线程需进行锁竞争及上下文切换,这会消耗系统资源。由于GIL的存在,Python进程在任何时刻只能执行一个线程(即持有GIL的线程),这导致在多核CPU上,Python多线程的执行效率往往不高。

GIL对并发模型的影响及策略

(2)在有全局锁的情况下如何运行多线程、多进程

此处进行分类讨论:

1、CPU密集型代码(如各类循环处理、计数等)。在此类场景下,计算任务繁重,ticks计数迅速达到阈值,触发GIL的释放与重新竞争(线程间的频繁切换消耗大量资源)。因此,Python下的多线程对CPU密集型代码并不友好,建议采用多进程方式实现多任务处理。

2、IO密集型代码(如文件处理、网络爬虫等)。多线程能显著提升效率。在单线程模式下,IO操作会导致等待时间浪费;而开启多线程后,当线程A进行IO等待时,系统自动切换至线程B,从而充分利用CPU资源,提升程序执行效率。因此,Python的多线程对IO密集型代码较为友好。

在Python 3.x版本中,GIL不再使用ticks计数,而是改用计时器(当执行时间达到阈值时,当前线程释放GIL)。这一改进对CPU密集型程序更为友好,但并未解决GIL导致同一时间只能执行一个线程的根本问题,因此整体效率仍有限。

需注意:在多核环境下,多线程的表现可能比单核下更差。原因在于单核多线程中,每次释放GIL后,唤醒的线程通常能直接获取GIL,实现无缝执行;而在多核环境中,CPU0释放GIL后,其他CPU上的线程会参与竞争,但GIL可能很快又被CPU0获取。这导致其他CPU上被唤醒的线程在等待切换时间后重新进入待调度状态,引发线程颠簸(thrashing),反而降低效率。

回到最初的问题:资深开发者常建议“在Python中想要充分利用多核CPU,请使用多进程”。其原因在于:每个进程拥有各自独立的GIL,互不干扰,从而实现真正意义上的并行执行。因此,在Python中,针对多核CPU,多进程的执行效率优于多线程。

代码实例与效率分析

(3)代码实例

使用一个线程进行计数


#encoing:utf-8
import threading
import time
def test_counter():
  i = 0
  for _ in range(100000000):
    i += 1
  return True
def main():
  start_time = time.time()
  for tid in range(2):
    t1 = threading.Thread(target=test_counter)
    t1.start()
    t1.join()
  end_time = time.time()
  print("Total time:{}".format(end_time-start_time))
if __name__ == "__main__":
  main()

结果:

使用2个线程执行非IO操作


#encoding:utf-8
import threading
import time
def test_counter():
  i = 0
  for _ in range(100000000):
    i += 1
  return True
def main():
  thread_array = {}
  start_time = time.time()
  for tid in range(2):
    t = threading.Thread(target=test_counter)
    t.start()
    thread_array[tid] = t
  for i in range(2):
    thread_array[i].join()
  end_time = time.time()
  print("Total time:{}".format(end_time-start_time))
if __name__ == "__main__":
  main()

结果:

通过上述代码可以得出结论:对于非IO类型操作,多线程因争夺GIL而导致程序执行效率降低。因此,应根据实际业务功能的需求,合理确定使用多线程或多进程。

总结

以上介绍了在Python全局锁机制下如何合理运用多线程与多进程,希望能为读者提供帮助。如有疑问,欢迎留言,小编会及时回复。感谢大家对本站的支持! 若本文对您有帮助,欢迎转载,请注明出处,谢谢!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表