小能豆

是否有任何内置函数会在 I/O 上阻塞而不允许其他线程运行?

py

今天我在该模块文档的“注意事项”部分看到了这个有趣的声明thread

并非所有可能阻塞等待 I/O 的内置函数都允许其他线程运行。(最流行的函数(time.sleep()file.read()select.select())可按预期工作。)

在我见过的几乎所有讨论 Python 线程的地方,人们总是假设所有执行 I/O 的内置函数都会释放 GIL,这意味着其他线程可以在函数阻塞时运行。据我所知,I/O 操作阻塞其他线程的唯一风险是,如果该操作是在有缺陷的 C 扩展上进行的,而忽略了释放 GIL。

那么,文档中的这个说法thread真的正确吗?是否有任何内置的阻塞 I/O 操作不会释放 GIL?到目前为止,我还没有找到任何具体的例子。


阅读 21

收藏
2024-11-12

共1个答案

小能豆

这是一个有趣的问题,Python 文档中关于这个问题的表述thread确实既令人好奇又含糊。让我们来分析一下情况。

在 CPython 中,I/O 密集型操作通常会释放全局解释器锁 (GIL),以便其他线程可以在阻塞操作(如文件读取或网络 I/O)完成时执行。这对于大多数标准库的 I/O 函数来说都是典型的,并且适用于使用固有释放 GIL 的系统调用的函数,例如time.sleep()select.select()和大多数file操作(read()write()等)。

然而,有几个细微差别需要考虑:

  1. 非 I/O 阻塞函数:一些涉及等待(但严格来说不是 I/O)的内置函数不会释放 GIL。例如,执行计算(例如大整数运算)或涉及 CPU 绑定活动的函数不会自动释放 GIL,因为它们不执行阻塞 I/O。
  2. 特定于平台的行为:由于系统调用处理 GIL 的方式不同,某些 I/O 函数的行为可能因平台而异。在极少数情况下,I/O 函数可能无法在某些平台上释放 GIL。这种情况并不常见,但可能发生在较低级别或专用的 I/O 函数上,具体取决于实现。
  3. C 扩展:正如您所提到的,第三方 C 扩展或某些内部 CPython 实现(虽然很少见)可能不会在应该释放 GIL 时释放。例如,C 扩展中的某些低级 I/O 操作或系统调用可能会忽略释放 GIL,这甚至可能在执行 I/O 时阻塞其他线程。
  4. 不太常用的内置操作:如果在未释放 GIL 的情况下实现一些不太常用的内置函数或操作,它们也可能会出现此问题。但是,诸如openreadwritesocket操作和标准文件处理例程之类的常用函数确实可以正确释放 GIL。

文档的声明是对潜在边缘情况的警告,但没有提供具体示例,因为这些情况可能很少见且特定于平台。通常,Python 标准库中的标准 I/O 函数的实现是为了适当地释放 GIL 以进行 I/O 绑定操作。

测试阻塞行为

如果您很好奇,可以测试特定函数在实际中是否会阻塞其他线程。您可以使用一个简单的多线程测试,其中一个线程执行疑似阻塞操作,另一个线程并行运行,检查延迟。但是,可以安全地假设,对于大多数常见的 I/O 函数,GIL 将按预期释放,特别是在 Linux 和 macOS 等经过充分测试的平台上。

2024-11-12