多处理多线程 GIL?

Nin*_*969 1 python multithreading multiprocessing

因此,自从几天以来,我对 python 上的多处理和多线程进行了大量研究,但我对很多事情感到非常困惑。很多次我看到有人在谈论 GIL 不允许 Python 代码在多个 cpu 内核上执行的内容,但是当我编写一个创建多个线程的程序时,我可以看到多个 cpu 内核处于活动状态。

第一个问题:什么是真正的 GIL?它有效吗?我想到了一些事情,比如当一个进程在多 CPU 上创建太多线程时,操作系统分布式任务。我对吗?

另一件事,我想利用我的 CPU。我想像创建与 cpu 内核一样多的进程,在此每个进程创建与 cpu 内核一样多的线程。我在正确的车道上吗?

Boo*_*boo 6

这里有关于什么是全局解释器锁(GIL)的答案。答案中隐藏着Python“字节码”的提及,这是问题的核心。当你的程序被编译时,输出是字节码,即虚构的“Python”计算机的低级计算机指令,由Python解释器解释。当解释器执行字节码时,它通过获取全局解释器锁来序列化执行。这意味着两个线程不能在两个不同的内核上同时执行字节码。这也意味着真正的多线程没有实现。但这是否意味着没有理由使用线程呢?不!以下是线程仍然有用的几种情况:

  1. 对于某些操作,解释器将释放 GIL,即在执行 I/O 时。因此,考虑一下您想要从不同网站获取大量 URL 的情况。大多数时间都花在等待发出请求后返回响应,并且即使必须串行地制定请求,这种等待也可以重叠。
  2. 许多Python函数和模块都是用C语言实现的,如果处理要求允许,在调用后可以自由释放GIL。该numpy模块就是这样一种高度优化的包。

因此,当任务不是 CPU 密集型时,最好使用线程,即它们需要大量等待 I/O 完成,或者它们需要大量休眠等。


spr*_*ksh 6

首先,GIL 仅确保在任何给定时间仅运行一个 cpython 字节码指令。它不关心哪个 CPU 内核运行指令。这是操作系统内核的工作。

所以回顾你的问题:

  1. GIL 只是一段代码。CPython 虚拟机是首先将代码编译为 Cpython 字节码的过程,但它的正常工作是解释 CPython 字节码。GIL 是一段代码,无论运行多少线程,它都能确保一次运行一行字节码。Cpython 字节码指令是构成虚拟机堆栈的内容。所以在某种程度上,GIL 将确保在任何给定时间点只有一个线程持有 GIL。(还有它不断为其他线程释放 GIL 而不会让它们挨饿。)

现在来到你真正的困惑。您提到当您运行具有多个线程的程序时,您可以看到多个(可能是全部)CPU 内核启动。所以我做了一些实验,发现你的发现是正确的(这是显而易见的),但在非线程版本中的行为也相似。

def do_nothing(i):
    time.sleep(0.0001)
    return i*2

ThreadPool(20).map(do_nothing, range(10000))
Run Code Online (Sandbox Code Playgroud)
def do_nothing(i):
    time.sleep(0.0001)
    return i*2

[do_nothing(i) for i in  range(10000)]
Run Code Online (Sandbox Code Playgroud)

第一个是多线程的,第二个不是。当您比较两个程序的 CPU 使用率时,您会发现在这两种情况下都会有多个 CPU 内核启动。所以你注意到的,虽然是对的,但与 GIL 或线程没有太大关系。多核 CPU 使用率高只是因为操作系统内核会根据可用性将代码的执行分配到不同的核。

您的最后一个问题更像是一个实验性的问题,因为不同的程序具有不同的 CPU/io 使用率。您只需要了解创建线程和进程的成本以及 GIL 和 PVM 的工作,并优化线程和进程的数量以获得最大性能。

您可以通过David Beazley 的这篇演讲来了解多线程如何使您的代码性能更差(或更好)。