也许你对 Python GIL 锁的理解是 错的。

简介: 也许你对 Python GIL 锁的理解是 错的。

摄影:产品经理甜白与草莓更配~

我刚到现在这个公司时,听到当时一个高级工程师(现已离职)大声地跟他旁边的同事说:

Python 有 GIL 锁,所以它的多线程实际上是单线程,所以写多线程代码不用考虑线程冲突,不用加锁。

相信现在看这篇文章的同学,不少人也是这样认为的。

然而,我要告诉你的是,这句话前半句是对的,后半句是 错的。Python 的多线程确实本质上是单线程。但你依然需要考虑线程并发冲突。

我们来举个例子:

场景 1:单线程

变量 a 的值为1.代码读取 a 的值,把它加 1 变成2.然后把2重新赋值给 a。代码再次读取 a 的值,把它加 1 变成3.然后重新把3复制给 a。最终 a 的值是3.

场景 2:伪多线程

Python 的多线程是伪多线程,意味着在微观上它是单线程。同一个时间,只有一个线程在运行,其他线程是暂停的状态。现在有一个变量 a,它里面的值为1.

  1. 线程 1 从 a 里面获取到1,把它加1,线程 1 还没有来得及把数字2重新赋值给变量 a 时,时间片切换到线程 2.
  2. 此时线程 1 暂停。线程 2 去读取变量 a,这个时候的 a 依然是1.线程 2 也把这个数字加1,变成2.
  3. 时间片切换到线程 1,线程 2 暂停。线程 1 把数字2赋值给变量 a,此时 a 的值变成2.
  4. 时间片切换到线程 2,线程 1 暂停。线程 2 把数字2赋值给变量 a,此时 a 的值还是2.

可以看到,即使同一时间只有一个线程在运行,但是两个线程同时修改同一个变量时,也会发生并发冲突。

GIL 到底锁的是什么?

大家都说 Python 有 GIL 锁,那么这个锁到底锁的是什么东西??

GIL 的全称是 Global Interpreter Lock, 全局解释器锁。它锁的是解释器而不是你的 Python 代码。它防止多线程同时执行 Python 的字节码(bytecodes),防止多线程同时访问 Python 的对象。

在 Python 官方文档Releasing the GIL from extension code[1]中,有这样一段话:

Here is how these functions work: the global interpreter lock is used to protect the pointer to the current thread state. When releasing the lock and saving the thread state, the current thread state pointer must be retrieved before the lock is released (since another thread could immediately acquire the lock and store its own thread state in the global variable). Conversely, when acquiring the lock and restoring the thread state, the lock must be acquired before storing the thread state pointer.

其中加黑的这一句话是说:GIL 锁用来保护指向当前进程状态的指针。

再看文档Thread State and the Global Interpreter Lock[2]中提到的这样一句话:

Without the lock, even the simplest operations could cause problems in a multi-threaded program: for example, when two threads simultaneously increment the reference count of the same object, the reference count could end up being incremented only once instead of twice.

当两个线程同时提高同一个对象的引用计数时,(如果没有 GIL 锁)那么引用计数只会被提高了 1 次而不是 2 次。

大家注意我这两段应用中的指针引用计数。其中指针是 C 语言的概念,Python 没有指针;引用计数是 Python 底层的概念。你平时写的 Python 代码,引用计数是在你调用变量的时候自动增加的,不需要你去手动加 1.

所以 GIL 锁住的东西,都是不需要你的代码直接交互的东西。

Python 的解释器通过切换线程来模拟多线程并发的情况,如上面举的例子,虽然同一个时间只有一个线程在活动,但仍然可以导致并发冲突。

所以,以后不要再说出 Python 不需要解决并发冲突这种话了。

目录
相关文章
|
5月前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
58 0
|
3月前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
22天前
|
Java C语言 Python
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
30 0
|
2月前
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
22 0
|
3月前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
3月前
|
消息中间件 存储 安全
python多进程并发编程之互斥锁与进程间的通信
python多进程并发编程之互斥锁与进程间的通信
|
3月前
|
安全 Python
Python 中的全局解释器锁(GIL)详解
【8月更文挑战第24天】
79 0
|
3月前
|
Python
python 协程 自定义互斥锁
【8月更文挑战第6天】这段代码展示了如何在Python的异步编程中自定义一个互斥锁(`CustomMutex`类)。该类通过`asyncio.Lock`实现,并提供`acquire`和`release`方法来控制锁的获取与释放。示例还包含了使用此自定义锁的场景:两个任务(`task1`和`task2`)尝试按序获取锁执行操作,直观地演示了互斥锁的作用。这有助于理解Python协程中互斥锁的自定义实现及其基本用法。
|
5月前
|
开发框架 并行计算 安全
Python的GIL限制了CPython在多核下的并行计算,但通过替代解释器(如Jython, IronPython, PyPy)和多进程、异步IO可规避
【6月更文挑战第26天】Python的GIL限制了CPython在多核下的并行计算,但通过替代解释器(如Jython, IronPython, PyPy)和多进程、异步IO可规避。Numba、Cython等工具编译优化代码,未来社区可能探索更高级的并发解决方案。尽管GIL仍存在,现有策略已能有效提升并发性能。
61 3
|
5月前
|
安全 Java Python
GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。
【6月更文挑战第20天】GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。线程池通过预创建线程池来管理资源,减少线程创建销毁开销,提高效率。示例展示了如何使用Python实现一个简单的线程池,用于执行多个耗时任务。
41 6