# 11.9 子解释器

到目前为止，你已经了解了：

* 多进程并行执行；
* 线程和异步并发执行。

多进程的缺点是使用管道和队列的进程间通信比共享内存慢，而且启动新进程的开销很大。

线程和异步的开销很小，但由于 GIL 中的线程安全保证而不能提供真正的并行执行。

第四个选项是 `subinterpreters`，它的开销比 `multiprocessing` 小，并且允许每个子解释器有一个 GIL。毕竟它是全局 **解释器** 锁。

在 CPython 运行时中，总是有一个解释器。解释器保存解释器状态，在一个解释器中你可以有一个或多个 Python 线程。解释器是求值循环的容器。它还管理自己的内存、引用计数器和垃圾收集。

CPython 具有用于创建解释器的低层次 C API，例如 `Py_NewInterpreter()`：

图片内容： Runtime State：运行时状态， Runtime：运行时， Interpreter 0： primary：主的， Interpreter State：解释器状态， GIL：不译， Thread 0：线程 0， Heap：堆， Core Instructions：核心指令， Modules：模块， Files：文件， Locks：锁， Sockets：套接字

{% hint style="info" %}
**注**

`subinterpreters` 模块在 `3.9` 中仍处于实验阶段，因此 API 可能会发生变化并且实现仍然存在问题。
{% endhint %}

因为解释器状态包含内存分配区域——所有指向 Python 对象（本地和全局）的指针的集合——子解释器无法访问其他解释器的全局变量。

与多进程类似，要在解释器之间共享对象，你必须将它们序列化或使用 `ctypes` 并使用一种 IPC（网络、磁盘或共享内存）。

## 相关源文件

以下是与子解释器相关的源文件：

| 文件                        | 用途               |
| ------------------------- | ---------------- |
| Lib/\_xxsubinterpreters.c | 子解释器模块的 C 实现     |
| Python/pylifecycle.c      | 解释器管理 API 的 C 实现 |

## 例子

在最后的示例应用程序中，必须在字符串中捕获实际的连接代码。在 3.9 中，子解释器只能用一串字符串代码来执行。

为了启动每个子解释器，线程列表以回调函数 `run()` 开始。

此函数将：

* 创建一个通信通道；
* 启动一个新的子解释器；
* 向子解释器发送要执行的代码；
* 通过通信通道接收数据；
* 如果端口连接成功，将其加入线程安全的队列。

`cpython-book-samples/33/portscanner_subinterpreters.py`

```python
import time
import _xxsubinterpreters as subinterpreters
from threading import Thread
import textwrap as tw
from queue import Queue

timeout = 1 # In seconds

def run(host: str, port: int, results: Queue):
    # Create a communication channel
    channel_id = subinterpreters.channel_create()
    interpid = subinterpreters.create()
    subinterpreters.run_string(
        interpid,
        tw.dedent(
    """
    import socket; import _xxsubinterpreters as subinterpreters
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    result = sock.connect_ex((host, port))
    subinterpreters.channel_send(channel_id, result)
    sock.close()
    """),
        shared=dict(
        channel_id=channel_id,
        host=host,
        port=port,
        timeout=timeout
        ))
    output = subinterpreters.channel_recv(channel_id)
    subinterpreters.channel_release(channel_id)
    if output == 0:
        results.put(port)

if __name__ == '__main__':
    start = time.time()
    host = "127.0.0.1" # Pick a host you own
    threads = []
    results = Queue()
    for port in range(80, 100):
        t = Thread(target=run, args=(host, port, results))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()
    while not results.empty():
        print("Port {0} is open".format(results.get()))
    print("Completed scan in {0} seconds".format(time.time() - start))
```

由于与多进程相比开销减少，此示例的执行速度应该加快 30% 到 40%，并且占用更少的内存资源：

```bash
$ python portscanner_subinterpreters.py
Port 80 is open
Completed scan in 1.3474230766296387 seconds
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hai-shi.gitbook.io/cpython-internals/11-parall-concur/11.9-subinterpreters.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
