内容提要
该PEP提出新增interpreters模块,用于在当前进程内检查、创建和运行多个子解释器,提供Interpreter对象及跨解释器通信的Queue类,并新增基于该模块的InterpreterPoolExecutor。子解释器严格隔离,不共享对象,仅通过队列传递可序列化数据,buffer对象可共享内存。模块还定义相关异常,并给出工作线程处理请求和分块处理大数据等示例。
延伸解读
子解释器与线程的关系
文章指出,子解释器是共享同一进程但严格隔离的运行时状态。每个子解释器拥有独立的模块、类和变量,不共享对象(少数不可变内置对象除外)。线程状态与解释器状态是一对多关系:一个线程状态只属于一个解释器,但一个解释器可有多个线程状态。理解这一点有助于区分线程并发与解释器隔离:线程共享同一解释器状态,而子解释器之间默认不共享可变数据。
通信机制与数据共享的边界
interpreters.Queue 是跨解释器通信的核心,仅支持可序列化对象,实际传递的是对象数据而非对象本身。大多数可变对象通过 pickle 复制,修改不会同步;但 buffer 协议对象(如 memoryview)会直接共享底层内存,需用户自行管理数据竞争。因此,队列适合传递消息和结果,而共享大块数据时应使用 memoryview 并设计同步机制,例如通过队列传递令牌来协调访问。
异常传播的设计考量
Interpreter.exec() 抛出的未捕获异常不会直接以原类型传播,而是包装为 ExecutionFailed,并保留完整回溯信息。这是因为解释器之间不能共享异常对象,且需要明确标识异常来自“远程”子解释器。若希望按原异常类型处理,应在传入 exec() 的代码内部使用 try-except。相比之下,Interpreter.call() 通过 pickle 传递,异常会直接抛出,更像本地调用。这一差异影响错误处理策略的选择。
InterpreterPoolExecutor 的定位
新增的 concurrent.futures.InterpreterPoolExecutor 派生自 ThreadPoolExecutor,每个工作线程运行在独立的子解释器中。与 ProcessPoolExecutor 类似,任务和参数通常会被序列化后发送给工作解释器,因此支持大部分可调用对象和参数。这比 Interpreter.call() 的初始限制更灵活。该执行器适合需要隔离全局状态但又想避免进程开销的场景,同时可通过初始化器设置 interpreters.Queue 实现工作器间通信。
Q&A
PEP 734 提出的 interpreters 模块主要用来做什么?
该模块用于在当前进程内检查、创建和运行多个子解释器,提供 Interpreter 对象和用于跨解释器通信的 Queue 类,并新增基于该模块的 InterpreterPoolExecutor。
Python 子解释器之间是如何隔离的?
子解释器严格隔离,不共享对象(除了少数不可变的内置对象),每个解释器有独立的模块、类、函数和变量,即使定义相同的类也会各自拥有副本。
如何在两个子解释器之间传递数据?
可以通过 interpreters.Queue 传递可序列化(可 pickle)的对象,队列会安全地跨解释器传递数据;对于 buffer 对象(如 memoryview),底层数据可共享内存而不序列化。
InterpreterPoolExecutor 和 ThreadPoolExecutor 有什么区别?
InterpreterPoolExecutor 是 ThreadPoolExecutor 的派生类,每个工作线程在自己的子解释器中执行任务,而 ThreadPoolExecutor 的所有线程共享同一个解释器。
Interpreter.exec() 中未捕获的异常会怎样处理?
未捕获的异常会以 ExecutionFailed 异常传播到调用解释器,并保留原始异常的完整错误显示(包括 traceback),但不会直接抛出原异常类型。
使用 interpreters 模块时,如何同步多个子解释器对共享资源的访问?
模块不提供专用的同步原语(如锁),但可以使用 interpreters.Queue 来管理共享资源,例如通过传递令牌(token)的方式控制访问顺序。