如果GIL存在,Python中的多线程有什么意义?

coo*_*278 7 python multithreading python-multithreading python-multiprocessing

据我了解,GIL使得不可能拥有分别利用内核的线程。

这是一个基本问题,但是threading库的意义是什么?如果线程代码的速度与普通程序相当,这似乎没有用。

Bha*_*rel 18

尽管存在 GIL,线程库仍然运行良好

在我解释之前,你应该知道Python的线程是真正的线程——它们是运行Python解释器的普通操作系统线程。GIL(或全局解释器锁)仅在运行纯Python代码时才被获取,并且在许多情况下被完全释放,甚至不被检查。

GIL不会阻止这些操作并行运行:

  1. IO操作,例如发送和接收网络数据读/写文件
  2. 大量内置 CPU 密集型操作,例如散列压缩
  3. 一些C扩展操作,例如numpy计算

其中任何一个(以及更多)都可以以并行方式完美运行,并且在大多数程序中,这些是花费最长时间的较重部分。

在 Python 中构建一个获取天文数据并计算轨迹的示例 API 意味着:

  • 处理输入和组装网络数据包将并行完成。
  • 如果轨迹计算在 numpy 中,那么它们都是并行的。
  • 将数据添加到数据库将是并行的。
  • 通过网络返回数据将是并行的。

基本上GIL不会影响绝大多数程序运行时。

此外,至少对于网络来说,目前其他方法更加流行,例如asyncio在同一线程上提供协作多任务处理,有效消除线程过载的缺点,并允许同时运行更多的连接。通过利用它,GIL 甚至不再相关。

GIL 可能是一个问题,并且在运行纯 Python 代码时占用 CPU 密集型的程序中,线程变得毫无用处,例如计算斐波那契数的简单程序,但在大多数现实情况下,除非您正在运行一个规模巨大的网站,例如与 Youtube(无可否认,它遇到了问题)一样,GIL 并不是一个重要的问题。

  • 谢谢回复。所以只是为了澄清一下,当使用 Python C 包装器(例如“file.open”、“file.write”、“file.read”、“socket.send”、“socket.recv”)进行 IO 时,Python 线程实际上是并行的吗? (2认同)
  • @redigaffi asyncio 有进一步的优化,例如使用 epoll / [IO 完成端口](https://learn.microsoft.com/en-us/windows/win32/fileio/io-completion-ports?redirectedfrom=MSDN)有效的单线程调度机制,各自使用不同的内核/网络驱动程序能力。无论 Python 是什么,许多速度提升都是在操作系统本身内实现的。 (2认同)

Rom*_*val 8

在某些情况下,应用程序甚至可能无法充分利用一个内核,而使用线程(或进程)可能有助于实现这一目标。

考虑一个典型的Web应用程序。它接收来自客户端的请求,对数据库进行一些查询,然后将数据返回给客户端。鉴于IO操作大多数时候都比CPU操作慢几个数量级,因此此类应用程序正在等待IO完成。首先,它等待从套接字读取请求。然后,它一直等到对数据库的请求写入打开到DB的套接字中。然后,它等待数据库的响应,然后将响应写入客户端套接字。

等待IO完成可能需要90%(或更长时间)的时间来处理请求。当单线程应用程序在IO上等待时,它只是不使用内核,并且该内核可用于执行。因此,这样的应用程序有空间让其他线程甚至在单个内核上执行。

在这种情况下,当一个线程等待IO完成时,它将释放GIL,而另一个线程可以继续执行。

  • 因此,我们可以得出结论,Python 线程模块在编写 IO 绑定程序时很有用。那是对的吗?还有其他情况需要考虑吗? (2认同)

Yes*_*ssy 5

严格来说,CPython 支持多 io-bound-thread + single-cpu-bound-thread。

  • I/O绑定方法file.open、、、、、等。当Python调用这些I/ file.writeO函数时,会释放GIL,并在I/O函数隐式返回后获取GIL 。file.readsocket.sendsocket.recv

  • CPU密集方法:算术计算等。

  • C 扩展方法:方法必须显式PyEval_SaveThread调用并告诉 Python 解释器您在做什么。PyEval_RestoreThread