"concurrent.interpreters" --- 同一进程中的多个解释器
****************************************************

Added in version 3.14.

**源代码：** Lib/concurrent/interpreters

======================================================================

"concurrent.interpreters" 模块在低层级的 "_interpreters" 模块之上构造
了更高层级的接口。

本模块的最初目标是提供一个基本 API 来管理解释器（也称“子解释器”）并在
其中运行任务。 运行过程涉及（在当前线程中）切换解释器并在执行上下文中
调用函数。

对于并发操作，解释器本身（以及本模块）并未提供状态隔离以外的更多手段，
对其自身而言用处不大。 实际的并发是通过 "线程" 单独提供的。 参见 below

参见:

  "InterpreterPoolExecutor"
     通过熟悉的接口将线程与解释器相结合。

  隔离扩展模块
     如何将扩展模块更新为支持多解释器。

  **PEP 554**

  **PEP 734**

  **PEP 684**

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__" 模块中运行的方式相同。


并发与并行
----------

如前所述，解释器本身并不提供任何并发能力。它们严格代表了运行时 *在当前
线程* 中将使用的隔离执行上下文。这种隔离特性使解释器与进程相似，但同时
又能像线程一样享受进程内的高效性。

尽管如此，解释器确实天然支持特定种类的并发。 这是该隔离机制的强大附带
影响。 它启用了一种不同于异步或线程的并发方式。 这是一种与It's a
similar concurrency model to CSP 或 actor 模型类似的并发模型，该模型可
以相对更容易地被理解。

开发者可以在单线程中利用这种并发模型，以无栈式风格在解释器之间来回切换
。然而，当将多解释器与多线程结合使用时，该模型才更能体现其价值。这种结
合主要涉及：启动新线程 → 切换至目标解释器 → 执行所需操作。

在Python中，每个实际线程（即使仅运行主线程）都拥有自己的 *当前* 执行上
下文。多个线程可以共享同一个解释器，也可以使用不同的解释器。

从高层次来看，可以将线程与解释器的组合理解为可选共享的线程模型。

一个重要优势是：解释器之间的隔离足够彻底，它们不共享 *GIL*，这意味着将
多线程与多解释器结合使用时，可以实现真正的多核并行处理。（该特性自
Python 3.12 起支持。）


解释器间通信
------------

在实际应用中，多解释器模式的价值取决于是否存在有效的通信机制。通常采用
消息传递方式实现交互，但在严格管控条件下也可共享数据。

基于此，"concurrent.interpreters" 模块提供了通过 "create_queue()" 访问
的 "queue.Queue" 实现。


"共享"对象
----------

在解释器间实际共享的任何数据都会丧失由 *GIL* 提供的线程安全性。扩展模
块可通过多种方案处理此问题，但在纯Python代码中，由于缺乏线程安全机制，
对象实际上无法真正共享（仅有少数例外）。这种情况下必须创建对象副本，意
味着可变对象无法保持同步状态。

默认情况下，当对象传递给其他解释器时，多数对象会通过 "pickle" 模块进行
复制。几乎所有不可变内置对象要么直接共享，要么会高效复制。例如：

* "None"

* "bool" ("True"  和 "False")

* "bytes"

* "str"

* "int"

* "float"

* "tuple" (仅限包含同类可支持对象时)

仅有少数Python类型能够真正在解释器间共享可变数据：

* "memoryview"

* "Queue"


参考
====

这个模块定义了以下函数：

concurrent.interpreters.list_all()

   返回一个 "Interpreter" 对象的 "list"，每个对象对应一个现有的解释器
   。

concurrent.interpreters.get_current()

   为当前运行的解释器返回一个 "Interpreter" 对象。

concurrent.interpreters.get_main()

   返回主解释器的 "Interpreter" 对象。该解释器是运行时为执行 *REPL* 或
   命令行脚本而创建的，通常也是唯一存在的解释器实例。

concurrent.interpreters.create()

   初始化一个新的（空闲的）Python解释器并为其返回一个 "Interpreter" 对
   象。

concurrent.interpreters.create_queue()

   初始化一个新的跨解释器队列，并返回其对应的 "Queue" 对象。


解释器对象
----------

class concurrent.interpreters.Interpreter(id)

   当前进程中的单个解释器。

   一般来说，不应该直接调用 "Interpreter"。 相反，使用 "create()" 或其
   他模块函数之一。

   id

      （只读）

      底层解释器的 ID。

   whence

      （只读）

      描述解释器来源的字符串。

   is_running()

      如果解释器当前正在执行其:mod:*!__main__`模块中的代码，则返回
      ``True`*，否则返回``False``。

   close()

      完成和销毁解释器。

   prepare_main(ns=None, **kwargs)

      将对象绑定到解释器的 "__main__" 模块中。

      部分对象会实际共享，部分对象可高效复制，但大多数对象仍需通过
      "pickle" 模块进行复制。具体参见 "共享"对象 。

   exec(code, /, dedent=True)

      在解释器中运行给定的源代码（在当前线程中）。

   call(callable, /, *args, **kwargs)

      返回在解释器中（在当前线程中）调用运行给定函数的结果。

   call_in_thread(callable, /, *args, **kwargs)

      在解释器中运行给定的函数（在一个新的线程中）。


异常
----

exception concurrent.interpreters.InterpreterError

   此异常是 "Exception" 的子类，在发生解释器相关错误时引发。

exception concurrent.interpreters.InterpreterNotFoundError

   此异常是 "InterpreterError" 的子类，当目标解释器不再存在时引发。

exception concurrent.interpreters.ExecutionFailed

   此异常是 "InterpreterError" 的子类，当运行的代码引发未捕获的异常时
   引发。

   excinfo

      在其他解释器中引发的异常的基本快照。

exception concurrent.interpreters.NotShareableError

   此异常是 "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()
