Python 您所在的位置:网站首页 python中多线程为啥要加锁 Python

Python

2024-06-28 17:47| 来源: 网络整理| 查看: 265

文章目录 什么是GIL?为什么会有GIL?GIL是Python的语言特性吗?没有GIL会发生什么?举例有GIL为什么还需要线程锁?附注

什么是GIL?

GIL(Global Interpreter Lock)全局解释器锁; GIL (Global Interpreter Lock) 是 Python 部分解释器的一个重要特性。GIL 是一个全局锁,它限制了【一个进程】一次只能有【一个线程】在运行 Python 解释器中的字节码。即使你的程序有多个线程,在任意时刻,只有一个线程可以执行 Python 代码。

为什么会有GIL?

GIL 的设计是为了解决 Python 的多线程环境中的同步问题。在 Python 中,所有的数据都是全局的,如果多个线程同时访问和修改共享数据,可能导致不一致性和数据损坏。

GIL 的设计是为了避免这种情况,使得所有的线程在任意时刻只有一个线程在执行 Python 代码。这保证了在多线程环境中,所有数据的一致性。

但是,GIL 的设计也带来了一些副作用,尤其是在 CPU 密集型任务中。GIL 的存在限制了 CPU 利用率,导致 Python 的多线程性能较差。因此,在 CPU 密集型任务中,通常使用多进程代替多线程;

在IO密集型任务中,虽然有GIL的存在,单多线程还是能够提升一定的执行效率。 因为在IO密集型任务中,使用CPU的时间并不多,主要瓶颈还是在IO等待上; 单线程和多线程进行比较时,会发现多线程的执行速度比单线程要快很多,因为大多数时间都在等待IO操作,单线程必须等前一个IO操作完成后才能处理后面的程序,而使用多线程时,若当前线程进入了IO操作,解释器会很快将切换其他线程进行运行,一定程度上将多个线程的IO操作变成了类【并行】运行,非IO程序的执行多线程还是在【并发】执行。

GIL是Python的语言特性吗? 不是

首先,Python是一门解释型的编程语言,在语言运行时会被逐行读取并解释执行; 这与编译型语言形成对比,编译性语言需要提前进行整体编译,运行时将直接执行编译过后的文件或内容,但预期带来的不便就是在维护代码时会变得复杂、繁琐且不灵活;

其次,既然是解释型语言,就需要有解释器进行解释翻译;Python常用的解释器有:CPython、IPython、Pypy、Jpython等; 通常大家使用的都是CPython,GIL是【CPython】与【CPython衍生解释器】(IPython等)的技术特性,针对于如Pypy、Jpython等解释器并不包含;

没有GIL会发生什么?

如果没有GIL,多线程程序将能够充分利用多核系统的计算能力,进一步提高程序的执行效率。但是,同时也会带来一些问题:

原子性问题:没有GIL保护,多个线程同时对同一个数据进行更新,很可能导致数据不一致的问题。

竞争条件问题:如果多个线程同时对共享资源进行操作,很可能会出现竞争条件,导致程序的不正确性。

同步问题:如果多个线程需要协调合作完成某项任务,就需要进行同步操作,保证多个线程的执行顺序。

如果没有GIL,就需要程序员自己解决这些问题,有些人认为GIL是CPython的一个缺陷,但其实GIL可以看作是一种取舍,既能保证解释器在多线程下的部分线程安全,又能减少程序员的工作量。

举例

举例一 假设有一个 Python 程序,它计算某个数的阶乘。该程序运行了一个很长的循环,并且在循环期间需要执行许多数学计算。

如果不使用 GIL,在多核 CPU 上运行该程序时,可能会有多个线程同时执行计算。这可能会导致多个线程试图修改同一内存区域,从而导致错误或数据丢失。

但是,由于 GIL 的存在,仅允许一个线程同时执行。这意味着,在多核 CPU 上运行该程序时,一次只有一个线程可以访问内存。因此,不会出现同时修改内存的问题,从而避免了数据竞争问题。

举例二 假设有一个 Python 程序,该程序在运行大量图像处理任务,每个任务都需要大量的 CPU 时间。如果该程序在单核 CPU 上运行,它将非常慢,因此我们希望将其运行在多核 CPU 上。

如果不使用 GIL,在多核 CPU 上运行该程序时,多个线程可能同时执行处理任务。这可能导致内存数据错误,因为多个线程可能会同时试图修改同一数据。

但是,由于 GIL 的存在,仅允许一个线程同时执行处理任务。这意味着,在多核 CPU 上运行该程序时,仅有一个线程可以同时处理图像,其他线程必须等待当前处理任务完成。

因此,GIL 的作用是保证在多线程 Python 程序中的内存安全,它确保仅有一个线程同时访问内存,从而避免了多线程并发访问导致的数据错误。

注意:GIL只保证解释器下的Python程序在多线程下不会出现系统内存错误,并【不】保证数据的【正确性】;

有GIL为什么还需要线程锁?

上面提到了GIL并不保证数据的正确性。

如果有一个文本,有两个线程,A线程向文本中写入1234,B线程向文本中写入ABCD,我们先启动线程A再启动线程B,期望得到1234ABCD或者ABCD1234的结果;

但GIL只能保证线程A、B向文件写入时不会出现内存错误(不会出现一个字符是1的上半部分和A的下半部分[不是一个有效的字符]),不能保证最后写入顺序变成1A2BC3D4或者1A2B3C4D等。

想要得到正确的结果就需要保证先执行完A线程,再执行B线程 或者 先执行完B线程,再执行完A线程。

就得给A线程中写入文件处加线程锁,就算线程被切换到B,应为程序被加锁,B无法获取到锁只能等待调度到A继续执行,直到A释放锁后,B才能进行文件写入;

上面只是举了一个不恰当的例子方便理解,通常在多线程进行大量数据运算或者大流量文件访问时会遇到现象;

附注

Python解释器

在运行 Python 程序时,会读取源代码并执行它。下面是 Python 解释器运行的简要流程:

读取源代码:首先,解释器读取源代码,并将其转换为字节码。编译字节码:然后,解释器将字节码编译为机器可以识别的代码。执行字节码:最后,解释器执行字节码,并产生结果。

这个流程可以在内存中进行,因此 Python 程序不需要预先编译。这使得 Python 程序更加灵活和易于维护。 同时,因为 Python 解释器在运行时对代码进行编译,因此可以对代码进行动态优化,从而提高代码的执行效率。

CPU 密集型任务与IO 密集型任务

CPU 密集型任务是指大量使用 CPU 计算的任务,如数学计算、图形处理等。这类任务需要大量使用 CPU 进行计算,因此需要快速的 CPU 处理速度来保证程序的效率。

IO 密集型任务是指大量需要等待 IO 操作的任务,如读写文件、网络通信等。这类任务需要大量的 IO 操作,因此需要优化 IO 操作的效率来保证程序的效率。

GIL与多进程

每个独立的Python进程都有单独的GIL。

GIL是Python解释器的一个全局锁,保证同一时刻只有一个线程可以执行Python代码。但是,每个独立的Python进程都有自己的内存空间,因此各个进程中的GIL互不影响。

因此,如果使用多个独立的Python进程,每个进程中都有自己的GIL,这些GIL互不影响,可以利用CPU的多核特性同时运行多个进程的多个线程(每个进程每时每刻只有一个线程在运行),从而充分利用多核处理器的优势。

多线程与多进程

Python官方并不明确地表示不推荐使用多线程,但是由于GIL的存在,使用多线程在Python中通常并不是最佳选择。

GIL限制了同一时刻只有一个线程可以执行Python代码,因此即使使用多线程,也无法充分利用多核处理器的优势。而多进程不受GIL的影响(因为每个进程每时每刻都有一个线程在跑,有几个进程,就相当于同时有多少个线程在跑),因此可以利用多核处理器的计算能力。

当然,多进程也有其自身的问题,例如进程间通信比线程间通信更复杂,并且内存消耗更多。但是,如果代码需要大量的 CPU 计算,多进程是更优秀的选择。

在 Python 中选择使用多线程还是多进程取决于具体需求:

对于多核 CPU 和高性能计算,多进程通常是更好的选择。对于I/O密集型任务,多线程可以适当提高执行效率。

🎉如果对你有所帮助,可以点赞、关注、收藏起来,不然下次就找不到了🎉

【点赞】⭐️⭐️⭐️⭐️⭐️ 【关注】⭐️⭐️⭐️⭐️⭐️ 【收藏】⭐️⭐️⭐️⭐️⭐️

Thanks for watching. –Kenny



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有