Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 311 – 简化扩展的全局解释器锁获取

作者:
Mark Hammond <mhammond at skippinet.com.au>
状态:
最终版
类型:
标准跟踪
创建日期:
2003年2月5日
Python 版本:
2.3
发布历史:
2003年2月5日,2003年2月14日,2003年4月19日

目录

摘要

本 PEP 提出了一种简化的 API,用于 Python 扩展模块访问全局解释器锁 (GIL)。具体来说,它为复杂的多线程扩展的作者提供了一个解决方案,在这些扩展中,Python 的当前状态(即 GIL 的状态)是未知的。

本 PEP 提出了一种新的 API,用于支持线程的平台,以管理 Python 线程状态。本文提出了一种实现策略,以及一个初步的、平台独立的实现。

基本原理

当前的 Python 解释器状态 API 适用于简单的单线程扩展,但对于非简单的多线程扩展来说,它很快就会变得异常复杂。

目前 Python 提供了两种处理 GIL 的机制

  • Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 宏。这些宏主要用于允许一个已经拥有 GIL 的简单 Python 扩展在进行“外部”(即非 Python)且通常代价高昂的调用时暂时释放它。任何正在等待 GIL 的现有 Python 线程都可以自由运行。虽然这对于从 Python 调用到外部世界的扩展来说是很好的,但对于在线程状态未知时需要调用 Python 的扩展来说,这没有任何帮助。
  • PyThreadStatePyInterpreterState API。这些 API 函数允许扩展/嵌入式应用程序获取 GIL,但它们面临一个严重的引导问题——在使用它们之前,您需要知道 Python 解释器和 GIL 的状态。一个特别的问题是对于需要处理 Python 从未见过的线程,但需要从该线程调用 Python 的扩展作者来说。编写一个这些“新”线程总是知道 GIL 的确切状态,从而可以可靠地与此 API 交互的扩展是非常困难、微妙且容易出错的。

由于这些原因,此类扩展应如何与 Python 交互的问题正迅速成为一个常见问题。本 PEP 的主要推动力是 python-dev 上的一个帖子[1],它立即指出了具有此确切问题的以下项目

  • win32all 扩展
  • Boost
  • ctypes
  • Python-GTK 绑定
  • Uno
  • PyObjC
  • Mac 工具箱
  • PyXPCOM

目前,对于这个问题没有合理、可移植的解决方案,迫使每个扩展作者都实现自己的定制版本。此外,问题很复杂,这意味着许多实现很可能不正确,导致各种问题,通常只会表现为“Python 已挂起”。

虽然现有线程状态 API 中最大的问题是无法查询锁的当前状态,但我们认为应该为扩展作者提供一个更完整、更简化的解决方案。这样的解决方案应该鼓励作者提供无错误、复杂的扩展模块,充分利用 Python 的线程机制。

限制和排除

本提案确定了针对具有复杂多线程需求但只需要一个“PyInterpreterState”的扩展作者的解决方案。不尝试迎合需要多个解释器状态的扩展。在撰写本文时,尚未发现需要多个 PyInterpreterStates 的扩展,而且尚不清楚该功能在 Python 本身中是否正常工作。

此 API 不会执行 Python 的自动初始化,也不会为多线程操作初始化 Python。扩展作者必须继续调用 Py_Initialize(),对于多线程应用程序,必须调用 PyEval_InitThreads()。这样做的原因是,第一个调用 PyEval_InitThreads() 的线程被 Python 指定为“主线程”,因此强制扩展作者指定主线程(通过要求他们进行第一次调用)消除了歧义。由于 Py_Initialize() 必须在 PyEval_InitThreads() 之前调用,并且由于这两个函数目前都支持多次调用,因此这给扩展作者带来的负担被认为是合理的。

此 API 旨在成为获取 Python GIL 所必需的全部。除了现有标准 Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 宏之外,假设扩展不会使用任何额外的线程状态 API 函数。具有此类复杂需求的扩展可以自由地继续使用现有线程状态 API。

提案

本提案建议向 Python 添加一个新的 API,以简化 GIL 的管理。此 API 将在所有定义了 WITH_THREAD 的平台上可用。

其目的是,假设 Python 已正确初始化,扩展作者可以在任何时候、在任何线程上使用一个小的、定义明确的“序言舞”,这将确保 Python 在该线程上准备好使用。在扩展完成 Python 后,它还必须执行“尾声舞”以释放以前获取的任何资源。理想情况下,这些舞步可以用一行代码表达。

具体来说,建议使用以下新 API

/* Ensure that the current thread is ready to call the Python
   C API, regardless of the current state of Python, or of its
   thread lock.  This may be called as many times as desired
   by a thread so long as each call is matched with a call to
   PyGILState_Release().  In general, other thread-state APIs may
   be used between _Ensure() and _Release() calls, so long as the
   thread-state is restored to its previous state before the Release().
   For example, normal use of the Py_BEGIN_ALLOW_THREADS/
   Py_END_ALLOW_THREADS macros are acceptable.

   The return value is an opaque "handle" to the thread state when
   PyGILState_Acquire() was called, and must be passed to
   PyGILState_Release() to ensure Python is left in the same state. Even
   though recursive calls are allowed, these handles can *not* be
   shared - each unique call to PyGILState_Ensure must save the handle
   for its call to PyGILState_Release.

   When the function returns, the current thread will hold the GIL.

   Failure is a fatal error.
*/
PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure(void);

/* Release any resources previously acquired.  After this call, Python's
   state will be the same as it was prior to the corresponding
   PyGILState_Acquire call (but generally this state will be unknown to
   the caller, hence the use of the GILState API.)

   Every call to PyGILState_Ensure must be matched by a call to
   PyGILState_Release on the same thread.
*/
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);

常见用法是

void SomeCFunction(void)
{
    /* ensure we hold the lock */
    PyGILState_STATE state = PyGILState_Ensure();
    /* Use the Python API */
    ...
    /* Restore the state of Python */
    PyGILState_Release(state);
}

设计与实现

PyGILState_Ensure() 的一般操作将是

  • 断言 Python 已初始化。
  • 为当前线程获取一个 PyThreadState,如果需要则创建并保存。
  • 记住锁的当前状态(已拥有/未拥有)
  • 如果当前状态未拥有 GIL,则获取它。
  • 增加当前线程上调用 PyGILState_Ensure 的次数计数器。
  • 返回

PyGILState_Release() 的一般操作将是

  • 断言我们的线程当前持有锁。
  • 如果旧状态表明锁以前是解锁的,则释放 GIL。
  • 递减线程的 PyGILState_Ensure 计数器。
  • 如果计数器 == 0
    • 释放并删除 PyThreadState
    • 忘记 ThreadState 被线程拥有。
  • 返回

假设如果单个线程使用两个独立的 PyThreadStates,则这是一个错误。pystate.h 中的注释(“State unique per thread”)支持此观点,尽管从未直接说明。因此,这将需要一些线程本地存储的实现。幸运的是,Python 源代码树中已经存在线程本地存储的平台独立实现,位于 SGI 线程端口中。此代码将集成到平台独立的 Python 核心中,但以这样一种方式,如果需要,平台可以提供更优化的实现。

实施

此提案的实现可以在 https://bugs.python.org/issue684256 找到

参考资料


来源: https://github.com/python/peps/blob/main/peps/pep-0311.rst

上次修改: 2025-02-01 08:59:27 GMT