concurrent.interpreters
--- 同一进程中的多个解释器¶
Added in version 3.14.
源代码: Lib/concurrent/interpreters
concurrent.interpreters
模块在低层级的 _interpreters
模块之上构造了更高层级的接口。
本模块的最初目标是提供一个基本 API 来管理解释器(也称“子解释器”)并在其中运行任务。 运行过程涉及(在当前线程中)切换解释器并在执行上下文中调用函数。
对于并发操作,解释器本身(以及本模块)并未提供状态隔离以外的更多手段,对其自身而言用处不大。 实际的并发是通过 线程
单独提供的。 参见 below
Availability: not WASI.
此模块在 WebAssembly 平台上无效或不可用。 请参阅 WebAssembly 平台 了解详情。
关键细节¶
在进一步深入之前,关于多解释器的使用需要注意以下几个要点:
默认情况下为 解释器隔离
没有隐式线程
并不是所有的PyPI包都支持在多个解释器中使用
概述¶
"解释器"本质上是 Python 运行时的执行上下文,它包含了运行时执行程序所需的所有状态,包括导入状态和内置对象等。(每个线程——即使只有主线程——除了当前解释器外,还拥有一些额外的运行时状态,这些状态与当前异常和字节码求值循环相关。)
解释器的概念和功能自 Python 2.2 版本起便已存在,但该特性此前仅能通过 C-API 使用且鲜为人知,同时 隔离 功能在 3.12 版本前相对不够完善。
多解释器与隔离¶
Python 实现方案可能支持在同一进程中使用多个解释器,CPython 就具备此功能。每个解释器实际上都是相互隔离的(仅有少量经过严格管控的进程级全局例外情况)。
这种隔离机制的主要价值在于为程序的不同逻辑组件提供严格隔离,使开发者能够精准控制这些组件之间的交互方式。
备注
从技术上讲,同一进程中的解释器永远无法实现严格隔离,因为在同一进程内对内存访问几乎没有任何限制。Python 运行时会尽力确保隔离性,但扩展模块很容易破坏这种隔离。因此,在安全敏感场景下——当不同解释器之间本不应相互访问数据时——请勿使用多解释器模式。
在一个解释器中运行¶
在另一个解释器中运行涉及两个步骤:首先在当前线程切换至目标解释器,然后调用目标函数。运行时将基于当前解释器的状态执行该函数。concurrent.interpreters
模块提供了一套基础API,用于创建和管理解释器,以及执行这种"切换-调用"操作。
该操作不会自动启动其他线程,但可通过 一个辅助工具 实现此功能。此外还提供了专用辅助工具,用于在解释器中调用内置函数 exec()
。
当在解释器中调用 exec()
(或 eval()
) 时,它们会使用该解释器的 __main__
模块作为"全局"命名空间来运行。对于未关联任何模块的函数也是如此。这与从命令行调用脚本时在 __main__
模块中运行的方式相同。
并发与并行¶
如前所述,解释器本身并不提供任何并发能力。它们严格代表了运行时 在当前线程 中将使用的隔离执行上下文。这种隔离特性使解释器与进程相似,但同时又能像线程一样享受进程内的高效性。
综上所述,解释器确实天然支持某些形式的并发。这种隔离性带来了一个重要的特性:它支持一种不同于异步编程或线程模型的并发实现方式,其并发模型与CSP(通信顺序进程)或Actor模型相似——而这类模型通常更易于推理分析。
开发者可以在单线程中利用这种并发模型,以无栈式风格在解释器之间来回切换。然而,当将多解释器与多线程结合使用时,该模型才更能体现其价值。这种结合主要涉及:启动新线程 → 切换至目标解释器 → 执行所需操作。
在Python中,每个实际线程(即使仅运行主线程)都拥有自己的 当前 执行上下文。多个线程可以共享同一个解释器,也可以使用不同的解释器。
从高层次来看,可以将线程与解释器的组合理解为可选共享的线程模型。
一个重要优势是:解释器之间的隔离足够彻底,它们不共享 GIL,这意味着将多线程与多解释器结合使用时,可以实现真正的多核并行处理。(该特性自 Python 3.12 起支持。)
解释器间通信¶
在实际应用中,多解释器模式的价值取决于是否存在有效的通信机制。通常采用消息传递方式实现交互,但在严格管控条件下也可共享数据。
基于此,concurrent.interpreters
模块提供了通过 create_queue()
访问的 queue.Queue
实现。
参考¶
这个模块定义了以下函数:
- concurrent.interpreters.list_all()¶
返回一个
Interpreter
对象的list
,每个对象对应一个现有的解释器。
- concurrent.interpreters.get_current()¶
为当前运行的解释器返回一个
Interpreter
对象。
- concurrent.interpreters.get_main()¶
返回主解释器的
Interpreter
对象。该解释器是运行时为执行 REPL 或命令行脚本而创建的,通常也是唯一存在的解释器实例。
- concurrent.interpreters.create()¶
初始化一个新的(空闲的)Python解释器并为其返回一个
Interpreter
对象。
解释器对象¶
- class concurrent.interpreters.Interpreter(id)¶
当前进程中的单个解释器。
一般来说,不应该直接调用
Interpreter
。 相反,使用create()
或其他模块函数之一。- id¶
(只读)
底层解释器的 ID。
- whence¶
(只读)
描述解释器来源的字符串。
- is_running()¶
如果解释器当前正在执行其:mod:!__main__`模块中的代码,则返回``True`,否则返回``False``。
- close()¶
完成和销毁解释器。
- prepare_main(ns=None, **kwargs)¶
将对象绑定到解释器的
__main__
模块中。
- exec(code, /, dedent=True)¶
在解释器中运行给定的源代码(在当前线程中)。
- call(callable, /, *args, **kwargs)¶
返回在解释器中(在当前线程中)调用运行给定函数的结果。
- call_in_thread(callable, /, *args, **kwargs)¶
在解释器中运行给定的函数(在一个新的线程中)。
异常¶
- exception concurrent.interpreters.InterpreterNotFoundError¶
此异常是
InterpreterError
的子类,当目标解释器不再存在时引发。
- exception concurrent.interpreters.ExecutionFailed¶
此异常是
InterpreterError
的子类,当运行的代码引发未捕获的异常时引发。- excinfo¶
在其他解释器中引发的异常的基本快照。
此异常是
TypeError
的子类,当一个对象无法发送到另一个解释器时引发。
解释器间通信¶
- class concurrent.interpreters.Queue(id)¶
这是一个对底层跨解释器队列的封装,实现了标准的
queue.Queue
接口。底层队列只能通过create_queue()
函数创建。部分对象会实际共享,部分对象可高效复制,但大多数对象仍需通过
pickle
模块进行复制。具体参见 "共享"对象 。- id¶
(只读)
队列的ID。
- exception concurrent.interpreters.QueueEmptyError¶
此异常继承自
queue.Empty
,当队列为空时,会由Queue.get()
和Queue.get_nowait()
方法引发。
- exception concurrent.interpreters.QueueFullError¶
此异常继承自
queue.Full
,当队列已满时,会由Queue.put()
和Queue.put_nowait()
方法引发。
基本使用¶
创建一个解释器并在其中运行代码::
from concurrent import interpreters
interp = interpreters.create()
# 在当前操作系统线程中运行。
interp.exec('print("spam!")')
interp.exec("""if True:
print('spam!')
""")
from textwrap import dedent
interp.exec(dedent("""
print('spam!')
"""))
def run(arg):
return arg
res = interp.call(run, 'spam!')
print(res)
def run():
print('spam!')
interp.call(run)
# 在新的操作系统线程中运行
t = interp.call_in_thread(run)
t.join()