Multiple interpreters in a Python process

虽然在大多数用例中,你都只会嵌入一个单独的 Python 解释器,但某些场景需要你在同一个进程甚至同一个线程中创建多个独立的解释器。 子解释器让你能够做到这一点。

“主”解释器是在运行时初始化时创建的第一个解释器。它通常是一个进程中唯一的 Python 解释器。与子解释器不同,主解释器具有唯一的进程全局责任比如信号处理等。它还负责在运行时初始化期间的执行并且通常还是运行时最终化期间的活动解释器。PyInterpreterState_Main() 函数将返回一个指向其状态的指针。

你可以使用 PyThreadState_Swap() 函数在子解释器之间进行切换。你可以使用下列函数来创建和销毁它们:

type PyInterpreterConfig

包含用于配置子解释器的大部分形参的结构体。其值仅在 Py_NewInterpreterFromConfig() 中被使用而绝不会被运行时所修改。

Added in version 3.12.

结构体字段:

int use_main_obmalloc

如果该值为 0 则子解释器将使用自己的“对象”分配器状态。否则它将使用(共享)主解释器的状态。

如果该值为 0check_multi_interp_extensions 必须为 1 (非零值)。如果该值为 1gil 不可为 PyInterpreterConfig_OWN_GIL 选项。

int allow_fork

如果该值为 0 则运行时将不支持在当前激活了子解释器的任何线程中 fork 进程。否则 fork 将不受限制。

请注意当 fork 被禁止时 subprocess 模块将仍然可用。

int allow_exec

如果该值为 0 则运行时将不支持在当前激活了子解释器的任何线程中通过 exec (例如 os.execv()) 替换当前进程。否则 exec 将不受限制。

请注意当 exec 被禁止时 subprocess 模块将仍然可用。

int allow_threads

如果该值为 0 则子解释器的 threading 模块将不会创建线程。否则线程将被允许。

int allow_daemon_threads

如果该值为 0 则子解释器的 threading 模块将不会创建守护线程。否则将允许守护线程(只要 allow_threads 是非零值)。

int check_multi_interp_extensions

如果该值为 0 则所有扩展模块均可在当前子解释器被激活的任何线程中被导入,包括旧式的 (单阶段初始化) 模块。否则将只有多阶段初始化扩展模块 (参见 PEP 489) 可以被导入。 (另请参阅 Py_mod_multiple_interpreters。)

如果 use_main_obmalloc0 则该值必须为 1 (非零值)。

int gil

这将确定针对子解释器的 GIL 操作方式。它可以是以下的几种之一:

PyInterpreterConfig_DEFAULT_GIL

使用默认选择 (PyInterpreterConfig_SHARED_GIL)。

PyInterpreterConfig_SHARED_GIL

使用(共享)主解释器的 GIL。

PyInterpreterConfig_OWN_GIL

使用子解释器自己的 GIL。

如果该值为 PyInterpreterConfig_OWN_GILPyInterpreterConfig.use_main_obmalloc 必须为 0

PyStatus Py_NewInterpreterFromConfig(PyThreadState **tstate_p, const PyInterpreterConfig *config)

新建一个子解释器。这是一个 (几乎) 完全隔离的 Python 代码执行环境。 特别需要注意,新的子解释器具有全部已导入模块的隔离的、独立的版本,包括基本模块 builtins, __main__sys 等。已加载模块表 (sys.modules) 和模块搜索路径 (sys.path) 也是隔离的。新环境没有 sys.argv 变量。它具有新的标准 I/O 流文件对象 sys.stdin, sys.stdoutsys.stderr (不过这些对象都指向相同的底层文件描述符)。

给定的 config 控制着初始化解释器所使用的选项。

成功后,tstate_p 将被设为新的子解释器中创建的第一个 thread state。该线程状态是 已附加的。请注意并没有真实的线程被创建;请参阅下文有关线程状态的讨论。如果新解释器的创建没有成功,则 tstate_p 将被设为 NULL;不会设置任何异常因为异常状态是存储在 attached thread state 中的,而它并不一定存在。

与所有其他 Python/C API 函数一样,调用此函数前必须存在 attached thread state,但返回时该状态可能会被分离。成功时,返回的线程状态将处于 已附加 状态。 如果子解释器使用自己的 GIL 创建,则调用解释器的 attached thread state 将被分离。 当函数返回时,新解释器的 thread state已附加 到当前线程,而先前解释器的 attached thread state 将保持分离状态。

Added in version 3.12.

子解释器在彼此相互隔离,并让特定功能受限的情况下是最有效率的:

PyInterpreterConfig config = {
    .use_main_obmalloc = 0,
    .allow_fork = 0,
    .allow_exec = 0,
    .allow_threads = 1,
    .allow_daemon_threads = 0,
    .check_multi_interp_extensions = 1,
    .gil = PyInterpreterConfig_OWN_GIL,
};
PyThreadState *tstate = NULL;
PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
if (PyStatus_Exception(status)) {
    Py_ExitStatusException(status);
}

请注意该配置只会被短暂使用而不会被修改。在初始化期间配置的值会被转换成各种 PyInterpreterState 值。 配置的只读副本可以被内部存储于 PyInterpreterState 中。

扩展模块将以如下方式在(子)解释器之间共享:

  • 对于使用多阶段初始化的模块,例如 PyModule_FromDefAndSpec(),将为每个解释器创建并初始化一个单独的模块对象。 只有 C 层级的静态和全局变量能在这些模块 对象之间共享。

  • For modules using legacy single-phase initialization, e.g. PyModule_Create(), the first time a particular extension is imported, it is initialized normally, and a (shallow) copy of its module's dictionary is squirreled away. When the same extension is imported by another (sub-)interpreter, a new module is initialized and filled with the contents of this copy; the extension's init function is not called. Objects in the module's dictionary thus end up shared across (sub-)interpreters, which might cause unwanted behavior (see Bugs and caveats below).

    请注意这不同于在调用 Py_FinalizeEx()Py_Initialize() 完全重新初始化解释器之后导入扩展时所发生的情况;对于那种情况,扩展的 initmodule 函数 会被 再次调用。 与多阶段初始化一样,这意味着只有 C 层级的静态和全局变量能在这些模块之间共享。

PyThreadState *Py_NewInterpreter(void)
属于 稳定 ABI.

新建一个子解释器。这在本质上只是针对 Py_NewInterpreterFromConfig() 的包装器,其配置保留了现有的行为。 结果是一个未隔离的子解释器,它会共享主解释器的 GIL,允许 fork/exec,允许守护线程,也允许单阶段初始化模块。

void Py_EndInterpreter(PyThreadState *tstate)
属于 稳定 ABI.

销毁由给定 thread state 表示的(子)解释器。给定的线程状态必须处于 已附加 状态。调用返回时,将不存在任何 attached thread state。与此解释器关联的所有线程状态都会被销毁。

Py_FinalizeEx() 将销毁所有在当前时间点上尚未被明确销毁的子解释器。

A per-interpreter GIL

Added in version 3.12.

使用 Py_NewInterpreterFromConfig() 你将可以创建一个与其他解释器完全隔离的子解释器,包括具有自己的 GIL。这种隔离带来的最大好处在于这样的解释器执行 Python 代码时不会被其他解释器所阻塞或者阻塞任何其他解释器。因此在运行 Python 代码时单个 Python 进程可以真正地利用多个 CPU 核心。这种隔离还能鼓励开发者采取不同于仅使用线程的并发方式。 (参见 PEP 554PEP 684 )。

使用隔离的解释器要求谨慎地保持隔离状态。尤其是意味着不要在未确保线程安全的情况下共享任何对象或可变的状态。由于引用计数的存在即使是在其他情况下不可变的对象 (例如 None, (1, 5)) 通常也不可被共享。 针对此问题的一种简单但效率较低的解决方式是在使用某些状态 (或对象) 时总是使用一个全局锁。或者,实际上不可变的对象 (如整数或字符串) 可以通过将其设为 immortal 对象而无视其引用计数来确保其安全性。事实上,对于内置单例、小整数和其他一些内置对象都是这样做的。

如果你能保持隔离状态那么你将能获得真正的多核计算能力而不会遇到自由线程所带来的复杂性。 如果未能保持隔离状态那么你将面对自由线程所带来的全部后果,包括线程竞争和难以调试的崩溃。

除此之外,使用多个相互隔离的解释器的一个主要挑战是如何在它们之间安全 (不破坏隔离状态)、高效地进行通信。运行时和标准库还没有为此提供任何标准方式。 未来的标准库模块将会帮助减少保持隔离状态所需的工作量并为解释器之间的数据通信(和共享)公开有效的工具。

错误和注意事项

Because sub-interpreters (and the main interpreter) are part of the same process, the insulation between them isn't perfect --- for example, using low-level file operations like os.close() they can (accidentally or maliciously) affect each other's open files. Because of the way extensions are shared between (sub-)interpreters, some extensions may not work properly; this is especially likely when using single-phase initialization or (static) global variables. It is possible to insert objects created in one sub-interpreter into a namespace of another (sub-)interpreter; this should be avoided if possible.

应当特别注意避免在子解释器之间共享用户自定义的函数、方法、实例或类,因为由这些对象执行的导入 操作可能会影响错误的已加载模块的 (子) 解释器的字典。 同样重要的一点是应当避免共享可被上述对象访问的对象。

还要注意的一点是将此功能与 PyGILState_* API 结合使用是很微妙的,因为这些 API 会假定 Python 线程状态与操作系统级线程之间存在双向投影关系,而子解释器的存在打破了这一假定。强烈建议你不要在一对互相匹配的 PyGILState_Ensure()PyGILState_Release() 调用之间切换子解释器。 此外,使用这些 API 以允许从非 Python 创建的线程调用 Python 代码的扩展 (如 ctypes) 在使用子解释器时很可能会出现问题。

高层级 API

type PyInterpreterState
属于 受限 API (作为不透明的结构体).

该数据结构代表多个合作线程所共享的状态。属于同一解释器的线程将共享其模块管理以及其他一些内部条目。该结构体中不包含公有成员。

最初归属于不同解释器的线程不会共享任何东西,但进程状态如可用内存、打开的文件描述符等等除外。全局解释器锁也会被所有线程共享,无论它们归属于哪个解释器。

在 3.12 版本发生变更: PEP 684 引入了 单解释器 GIL 的可能性。请参阅 Py_NewInterpreterFromConfig() 函数。

PyInterpreterState *PyInterpreterState_Get(void)
属于 稳定 ABI 自 3.9 版起.

获取当前解释器。

如果没有 attached thread state 则会发生致命错误。 此函数不会返回 NULL。

Added in version 3.9.

int64_t PyInterpreterState_GetID(PyInterpreterState *interp)
属于 稳定 ABI 自 3.7 版起.

返回解释器的唯一 ID。如果执行过程中发生任何错误则将返回 -1 并设置错误。

调用方必须有已附加的线程状态 attached thread state

Added in version 3.7.

PyObject *PyInterpreterState_GetDict(PyInterpreterState *interp)
返回值:借入的引用。 属于 稳定 ABI 自 3.8 版起.

返回一个存储解释器专属数据的字典。如果此函数返回 NULL 则没有任何异常被引发并且调用方应当将解释器专属字典视为不可用。

这不是 PyModule_GetState() 的替代,扩展仍应使用它来存储解释器专属的状态信息。

返回的字典是从解释器借入的并将保持可用直到解释器关闭。

Added in version 3.8.

typedef PyObject *(*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)

帧评估函数的类型

throwflag 形参将由生成器的 throw() 方法来使用:如为非零值,则处理当前异常。

在 3.9 版本发生变更: 此函数现在可接受一个 tstate 形参。

在 3.11 版本发生变更: frame 形参由 PyFrameObject* 改为 _PyInterpreterFrame*

_PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp)

获取帧评估函数。

请参阅 PEP 523 "Adding a frame evaluation API to CPython"。

Added in version 3.9.

void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame)

设置帧评估函数。

请参阅 PEP 523 "Adding a frame evaluation API to CPython"。

Added in version 3.9.

低层级 API

下列所有函数都必须在 Py_Initialize() 之后被调用。

在 3.7 版本发生变更: 现在 Py_Initialize() 会初始化 GIL 并设置一个 attached thread state 线程状态。

PyInterpreterState *PyInterpreterState_New()
属于 稳定 ABI.

新建一个解释器状态对象。不需要有 attached thread state,但如果有必要序列化对此函数的调用则可能选择有。

引发一个不带参数的 审计事件 cpython.PyInterpreterState_New

void PyInterpreterState_Clear(PyInterpreterState *interp)
属于 稳定 ABI.

重置解释器状态对象中的所有信息。解释器必须存在一个 attached thread state

引发一个不带参数的 审计事件 cpython.PyInterpreterState_Clear

void PyInterpreterState_Delete(PyInterpreterState *interp)
属于 稳定 ABI.

销毁一个解释器状态对象。目标解释器 不应 存在 attached thread state。在调用此函数之前,必须先调用 PyInterpreterState_Clear() 重置解释器状态。

Advanced debugger support

这些函数仅供高级调试工具使用。

PyInterpreterState *PyInterpreterState_Head()

返回由所有此类对象组成的列表中位于开头的解释器状态对象。

PyInterpreterState *PyInterpreterState_Main()

返回主解释器状态对象。

PyInterpreterState *PyInterpreterState_Next(PyInterpreterState *interp)

从由解释器状态对象组成的列表中返回 interp 之后的下一项。

PyThreadState *PyInterpreterState_ThreadHead(PyInterpreterState *interp)

在由与解释器 interp 相关联的线程组成的列表中返回指向第一个 PyThreadState 对象的指针。

PyThreadState *PyThreadState_Next(PyThreadState *tstate)

从由属于同一个 PyInterpreterState 对象的线程状态对象组成的列表中返回 tstate 之后的下一项。