程序员阿沛
发布于 2026-06-27 / 0 阅读
0
0

腾讯一面解释Python中的GIL全局解释器锁和它的底层原理GIL锁和互斥锁的区别是什么

腾讯一面:解释Python中的GIL(全局解释器锁)和它的底层原理?GIL锁和互斥锁的区别是什么?

面试官:请你说说Python中GIL锁的作用和影响?

Python中的GIL(Global Interpreter
Lock,全局解释器锁)是Python解释器(CPython)中用于多线程编程的一个机制。GIL是一个互斥锁,它确保了任何时候只有一个Python线程能够执行Python字节码。这意味着在多线程环境下,虽然操作系统能够同时运行多个线程,但Python的GIL限制了Python代码在同一时间只能被一个线程执行。

为什么需要GIL?

GIL的存在主要是为了简化Python的内存管理。Python的内存管理(如对象分配和垃圾回收)不是线程安全的,如果多个线程能够同时执行Python代码,那么内存管理将变得非常复杂且容易出错。GIL通过确保任何时候只有一个线程能够执行Python代码,从而避免了这种复杂性。

GIL的影响

GIL对Python多线程程序的性能有显著影响:

  1. CPU密集型任务 :对于需要大量计算的任务,GIL会成为瓶颈,因为它限制了多线程并行执行的能力。在这种情况下,使用多进程(multiprocessing模块)通常比多线程更有效。

  2. I/O密集型任务 :对于涉及大量等待I/O操作(如网络请求、文件读写)的任务,GIL的影响较小。因为当线程等待I/O操作时,它会释放GIL,允许其他线程运行。因此,对于I/O密集型任务,多线程仍然可以带来性能上的好处。

绕过GIL的方法

虽然GIL限制了多线程在某些情况下的性能,但Python程序员仍然有几种方法可以绕过GIL的限制:

  • 多进程 :使用multiprocessing模块,可以创建多个独立的Python解释器进程,每个进程都有自己的Python对象和GIL。这样,不同的进程可以并行执行Python代码,绕过单个GIL的限制。

  • C扩展模块 :通过编写C扩展模块,可以释放GIL,并在C代码中执行线程安全的操作。这对于需要高性能且能够处理多线程并行执行的任务特别有用。

  • 异步编程 :使用asyncio模块,可以编写异步代码,通过事件循环来管理I/O操作,而不是依赖多线程或多进程。这种方法特别适合于I/O密集型任务。

面试官:说说看GIL锁的底层原理,在多核的环境下,Python多线程能使用到多核吗?

GIL锁的底层原理

上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会尝试获取
GIL锁,以阻止别的线程执行,但只有其中一个线程能够获取成功;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。

线程释放GIL锁有两种情况,一是遇到 IO操作 ,二是 Time Tick 到期。

IO操作很好理解,比如线程 发出一个http请求,该线程 等待响应的过程中就会释放GIL锁给其他线程执行 。

那么 Time Tick 到期是什么呢? Time Tick规定了线程的最长执行时间,超过时间后自动释放GIL锁。 Python 3
以后,间隔时间大致为 15毫秒 。这就类似于操作系统管理进程中的时间片概念,只不过 Time Tick
是属于Python解释器自己的时间片。

虽然都是释放GIL锁,但这两种情况是不一样的。比如,Thread1遇到 IO操作
释放GIL,由Thread2和Thread3来竞争这个GIL锁,Thread1 不再参与这次竞争

如果是Thread1因为 Time Tick
到期释放GIL(多数是CPU密集型任务),那么三个线程可以同时竞争这把GIL锁,可能出现Thread1在竞争中胜出,再次执行的情况。单核CPU下,这种情况不算特别糟糕。因为只有1个CPU,所以CPU的利用率是很高的。

在多核CPU下,由于GIL锁的全局特性,虽然多线程可能分布在多个核上,但却无法发挥多核的并行特性。

如图所示:一个Python进程中有Thread1 和 Thread2
两个线程在工作。虽然Thread1在CPU1上运行,Thread2在CPU2上运行,但是同一时间只会有1个CPU在为这个Python进程工作。

GIL是全局的,CPU2上的Thread2需要等待CPU1上的Thread1让出GIL锁,才有可能执行。如果在多次竞争中,Thread1都胜出,Thread2没有得到GIL锁,意味着对于该Python进程而言,CPU2一直是闲置的,无法发挥多核的优势。

为了避免同一线程一直霸占CPU,在 python3.2 版本之后,线程会自动的调整自己的优先级,使得多线程任务执行效率更高。

面试官:** **既然GIL锁能保证同一时刻Python进程只有一个线程在执行,那么GIL能否绝对保证线程安全?

GIL不能绝对保证线程安全。 GIL虽然能 确保同一时间只有一个线程能够执行Python字节码。
然而,这种机制主要关注的是Python解释器级别的线程安全,而不是应用程序级别的线程安全。

举个例子:

def add():    global n    for i in range(10**1000):        n = n +1def sub():    global n    for i in range(10**1000):        n = n - 1n = 0import threadinga = threading.Thread(target=add,)b = threading.Thread(target=sub,)a.start()b.start()a.join()b.join()print n

上面的程序对n做了同样数量的加法和减法,那么n理论上是0。但运行程序,打印n,发现它不是0。问题出在哪里呢,问题在于python的每行代码不是原子化的操作。比如n
= n+1这步,不是一次性执行的。如果去查看python编译后的字节码执行过程,可以看到如下结果。

19 LOAD_GLOBAL              1 (n)22 LOAD_CONST               3 (1)25 BINARY_ADD          26 STORE_GLOBAL             1 (n)

从过程可以看出, n = n +1 操作分成了四步完成。因此, n = n+1 不是一个原子化操作。

  • 1.加载全局变量n

  • 2.加载常数1

  • 3.进行二进制加法运算

  • 4.将运算结果存入变量n。

根据前面的线程释放GIL锁原则,线程a执行这四步的过程中,有可能会让出GIL。如果这样,n=n+1的运算过程就被打乱了。最后的结果中,得到一个非零的n也就不足为奇。

因此,GIL无法保护跨线程的数据共享和竞争条件。如果多个线程同时访问和修改共享数据,而没有适当的同步机制(如锁、信号量等),则可能会导致数据不一致和竞态条件。

面试官:既然说到了同步机制,那说说看GIL锁和互斥锁有什么区别?

线程互斥锁和GIL的区别

1.线程互斥锁是Python代码层面的锁,解决Python程序中多线程共享资源的问题;

2.GIL是Python解释层面的锁,解决解释器中多个线程的竞争资源问题,而GIL锁本质也是一个互斥锁。


欢迎在评论区留言表达看法 或 提出你想学习的技术内容 与 面试问题,阿沛会将其作为往后更新的内容。

如果本文对大家有帮助,麻烦大家动动小手点个免费的“赞”或“在看”,大家的鼓励就是阿沛持续更新的动力~

-- 往期精彩回顾 –

美团一面:你能阐述一下CAP理论的基本概念和核心思想?说说它有哪些分布模型以及如何抉择?

美团二面:请你说说看分布式事务中的两阶段提交(2PC)的过程以及可能发生的问题

字节二面:请讲一下程序的内存分区/内存模型?

面试题:什么时候会出现TCP粘包,如何解决TCP粘包

操作系统入门(六) 内存篇之30张图爆肝内存连续分配管理、页式存储、段式存储和段页式存储


评论