17611538698
webmaster@21cto.com

Python 3的 GIL,多线程与多处理实践

编程语言 0 88 2024-10-20 06:43:00

图片

导读:本篇文章将讲解 Python 全局解释器锁 (GIL),它可以防止多个线程同时执行 Python 代码。

编程语言不一定有争议,但Python 的全局解释器锁 (GIL)却是一个备受争议的话题。

GIL 继续将 Python 的执行线程绑定到一个 CPU,从而导致瓶颈和延迟。那么为什么我们仍然需要 GIL,解决方法是什么?

什么是 GIL?


Python 的 GIL 和Python作为一种语言的出现都早于我们目前生活的多核 CPU 世界。


第一个多核处理器诞生于 21 世纪初,大概是在 Python 诞生的 10 年后。在为机器添加多个核心成为最佳实践之前,加速系统的主要方法是增强系统的单个 CPU。这意味着在 Python 创建时,此编程语言始终与单个 CPU 绑定在一起。


这就是 GIL 派上用场的地方。Python 的内存管理不是线程安全的,这意味着两个线程不能同时访问同一内存,否则会有数据损坏的风险。因此,即使在所有 CPU 密集型任务都在单个 CPU 上进行的时代,不仅必须有顺序,而且必须由开发者而不是 CPU 或运气来决定操作顺序。


有了 GIL 充当巨大的锁,执行线程就可以与开发者的计划保持一致,内存安全也得以维持。


但是,现在我们生活在一个多核的世界,Python 是许多计算密集型操作的首选语言,这些操作可能更适合由多个 CPU 来执行,那么为什么不完全删除 GIL 呢?


这个过程需要根本性的、突破性的改变——删除 GIL 意味着改变 Python 的内存处理方式。


Python 多处理和多线程


多处理和多线程是将较大的执行线程分解为较小线程的两种方法。


多线程


对于 I/O 密集型任务,多线程是一个不错的选择。


多线程是指处理器同时执行多个线程的过程。这些线程在同一 CPU 上同时并行运行,因此在父进程中共享相同的内存空间。多线程可以节省系统内存、提高计算速度并提高应用程序性能。响应式 UI 是经常使用多线程的一个用例。


Python 通过其线程模块支持多线程。下面的代码片段包括两个线程的设置与执行。




mport threading
def numbers(): for i in range(1, 5): print("Thread 1:", i)
def letters(): for letter in ['a', 'b', 'c', 'd', 'e']: print("Thread 2:", letter)
# Create threadsthread1 = threading.Thread(target=numbers)thread2 = threading.Thread(target=letters)
# Start threadsthread1.start()thread2.start()
# Wait for threads to finishthread1.join()thread2.join()
print("Example Complete.")


numbers()和letters()函数都表示一个任务部分,该部分被分离到自己的线程中。下一步是创建一个Thread,具有目标函数的对象以供执行。start()方法启动每个线程,并join()等待线程执行完毕后,再继续执行程序。

图片

多线程也存在缺点。多线程仍然受 GIL 功能的约束。代码本身可能更难阅读,从而导致更严格的故障排除与调试过程。

此外,多线程进程不能被中断。

多处理


多处理是 Python 开发人员可以摆脱 GIL 限制的方式。这可使其成为 CPU 密集型任务的绝佳选择,它比单处理器系统更具成本效益和效率。多处理类似于线程,但工作负载分散在多个 CPU 上,每增加一个 CPU 都会提高速度、功率和内存容量。


多处理需要比线程更多的内存存储空间来在进程之间移动数据。必须实现进程间通信 (IPC) 模型才能在进程之间共享对象。


还有一个支持多处理的 Python 模块。虽然与多线程不同,但代码看起来是非常相似的。


再也不要害怕!


即使有 GIL,你也不必通过多线程和多处理一次处理数十亿个数据点。根据数据表明 Google搜索,会发现有关 GIL 话题有数千条搜索,以及谁希望或不希望它消失。目前这个 GIL,确实是 Python 最具争议的话题。


今天先到这里,祝大家周末愉快~

作者:场长

评论