5 июня 2025 года был принят PEP-0734. Судя по информации на официальном сайте, он является продолжением PEP-0554. Этот PEP предлагает добавить новый модуль interpreters
для поддержки проверки, создания и запуска кода в нескольких интерпретаторах в текущем процессе. А если идти дальше, то он является продолжением PEP-0684, который предлагает один GIL на интерпретатор.
Несколько полноценных интерпретаторов работающих рядом. Какие плюсы?
Один процесс;
Один тред, но руками можно создавать еще;
Данные между интерпретаторами всегда передаются через сериализацию, аналогичную pickle, включая примитивные типы;
По GILу на интерпретатор, все еще можно получить плюшки настоящей многозадачности по сети;
Работает с asyncio.
GIL (Global Interpreter Lock) в Python — глобальная блокировка интерпретатора. Это механизм, встроенный в стандартную реализацию Python (CPython), который предотвращает одновременное выполнение байт-кода Python несколькими потоками.
Среди минусов — данный PEP значительно изменил C-код, и поэтому не всегда гарантируется стабильность C-расширений. Кстати, о том, как их создавать, я рассказывал в своей прошлой статье.
Есть несколько важных нетехнических аспектов про процесс создания данной фичи:
PEP-734 и Free-Threading делают очень похожие вещи – позволяют реализовывать настоящую многозадачность, но разными способами;
Изначально субинтерпретаторы появились в 3.10 в виде только C-API;
Есть отдельный PyPI пакет (https://pypi.org/project/interpreters-pep-734/) с данным кодом;
Python часть в виде PEP-734 был добавлен в 3.14 уже после feature freeze;
Изначально планировалось добавить его как модуль
interpreters
, однако в последний момент он сталconcurrent.interpreters
, вот тут доступно большое обсуждение.
PEP добавляет модуль interpreters
(concurrent.interpreters
). Этот включает объекты Interpreter
, представляющие базовые интерпретаторы. Модуль также предоставляет базовый класс Queue
(очереди) для связи между интерпретаторами.
Для пользователей будет простой API:
interp = interpreters.create()
try:
interp.exec('print("Hello from PEP-554")')
finally:
interp.close()
Прямо сейчас, если использовать Python 3.14, можно импортировать пакет concurrent.interpreters
:
import concurrent.interpreters as interpreters
interp = interpreters.create()
a = 15
print(f"A in main: {a}")
try:
interp.exec('print("Hello from PEP-554")\na = 10\nprint(f"A in subinterp: {a}")')
finally:
interp.close()
Вывод при запуске:
A in main: 15
Hello from PEP-554
A in subinterp: 10
❯ Почему этот PEP важен?
Модуль interpreters
предоставит высокоуровневый интерфейс для функциональности множественных интерпретаторов. Цель состоит в том, чтобы сделать существующую функцию множественных интерпретаторов CPython более доступной для кода Python. Это особенно актуально сейчас, когда CPython имеет GIL для каждого интерпретатора (PEP 684), и люди больше заинтересованы в использовании множественных интерпретаторов.
Без модуля stdlib пользователи ограничены C API , что ограничивает их возможности экспериментировать и использовать преимущества нескольких интерпретаторов.
Модуль будет включать базовый механизм для общения между интерпретаторов. Без него несколько интерпретаторов будут гораздо менее полезной функцией.
❯ Устройство
По сути, «интерпретатор» — это коллекция (по сути) всех состояний времени выполнения, которые потоки Python должны совместно использовать.
Процессы в питоне могут иметь один или больше потоков ОС, выполняющих python-код (или которые взаимодействуют с C API). Каждый из этих потоков работает с рантаймом CPython.
Интерпретаторы создаются через C API с помощью Py_NewInterpreterFromConfig()
(или Py_NewInterpreter()
, который является легкой оберткой вокруг Py_NewInterpreterFromConfig()
). Эта функция делает следующее:
Создает новое состояние;
Создает новое состояние потока;
Устанавливает состояние потока как текущее (текущее состояние необходимо для инициализации интерпретатора);
Инициализирует состояние интерпретатора, используя состояние потока;
Возвращает состояние потока (все еще актуальное).
Когда запускается процесс Python, он создает одно состояние интерпретатора («главный» интерпретатор) с одним состоянием потока для текущего потока ОС. Затем среда выполнения Python инициализируется с их использованием.
После инициализации скрипт или модуль или REPL выполняется с их помощью. Это выполнение происходит в модуле интерпретатора __main__
.
Когда процесс завершает выполнение запрошенного кода Python или REPL в основном потоке ОС, среда выполнения Python завершается в этом потоке с использованием основного интерпретатора.
❯ C API
Внутри можно найти много различных C-модулей. Давайте разберем их подробнее.
В данном файле находится API для управления действиями между изолированными интерпретаторами. Основа, в общем.
Некоторые функции мы опустим, если они малозначительные (по типу _Py_GetMainfile
), вы их сможете просмотреть сами.
Основные функции:
runpy_run_path
вызывает запускrunpy
вместе с путем;set_exc_with_cause
создает исключение с причиной.
-
Управление интерпретаторами:
_PyXI_NewInterpreter()
: Создает новый изолированный интерпретатор_PyXI_EndInterpreter()
: Завершает работу интерпретатора_Py_CallInInterpreter()
: Выполняет функцию в другом интерпретаторе
-
Межязыковые данные:
_PyXIData_t
: Структура для передачи данных между интерпретаторами_PyObject_GetXIData()
: Преобразует объект в межъязыковой формат_PyXIData_NewObject()
: Воссоздает объект из межъязыковых данных
-
Сериализация:
_PyPickle_GetXIData()
: Использует pickle для сериализации объектов_PyMarshal_GetXIData()
: Использует marshal для сериализации кода
-
Управление сессиями:
_PyXI_session
: Сессия выполнения в другом интерпретаторе_PyXI_Enter()
: Начало сессии в другом интерпретаторе_PyXI_Exit()
: Завершение сессии
-
Обработка ошибок:
_PyXI_excinfo
: Сохранение информации об исключениях между интерпретаторами_PyXI_failure
: Унифицированная обработка сбоев
Среди особенностей реализации можно выделить изоляцию главного (__main__
) модуля для каждого интерпретатора.
Все данные передаются безопасно, делиться простыми можно без необходимости использовать pickle. Для сложных объектов же потребуется сериализация.
Поддерживается асинхронное выполнение вызовов и обработка памяти через флаг _Py_PENDING_RAWFREE
.
Кроме этого, не стоит забывать про обработку исключений. Происходит сериализация исключений через _PyXI_excinfo
, трассировка преобразуется в TracebackException, Реализованы методы распространения ошибок между интерпретаторами.
Данный модуль отвечает за низкоуровневый доступ к примитивам интерпретаторов. Определение самих интерпретаторов.
Модуль предоставляет низкоуровневый API для работы с интерпретаторами Python, включая:
Создание и уничтожение интерпретаторов
Управление изоляцией между интерпретаторами
Выполнение кода в разных интерпретаторах
Межъядерную передачу данных
Управление конфигурацией интерпретаторов
В коде можно увидеть C-функцию interp_create
, которая является реализацией create()
. Он создает новый интерпретатор с указанной конфигурацией интерпретатора. После можно увидеть interp_destroy
для уничтожения объекта интерпретатора (в python это destroy()
). Также есть list_all
для получения списка всех интерпретаторов в текущем модуле, а также get_current
и get_main
методы для получения текущего и главного интерпретатора.
Также можно выделить отдельный список функций для выполнения кода: exec()
для выполнения произвольного кода, run_string
для выполнения строки кода, run_func()
для выполнения тела функции, и call()
для вызова callable-объекта (включая callable-классы) с аргументами).
Для межъядерного и многопроцессорного взаимодействия идет реализация безопасного разделения буферов между интерпретаторами:
typedef struct {
PyObject base;
Py_buffer *view;
int64_t interpid;
} xibufferview;
Для сереализации и десереализации сложных объектов есть механизм _PyXIData
. Он используется в качестве аргумента к функциям где идет работа со сложными объектами. А также есть поддержка разделяемых объектов данных через параметры shared
.
Кроме того, есть функции управления состояниями интерпретаторов. Состояния потоков связаны с состояниями интерпретатора примерно так же, как связаны потоки и процессы ОС (на высоком уровне). Для начала, связь — один ко многим. Состояние потока принадлежит одному интерпретатору (и хранит указатель на него). Это состояние потока никогда не используется для другого интерпретатора. Однако в обратном направлении интерпретатор может иметь ноль или более состояний потоков, связанных с ним. Интерпретатор считается активным только в потоках ОС, где одно из его состояний потоков является текущим.
Функция set___main___attrs
устанавливает атрибуты в __main__
модуль, а capture_exception
нужна для захвата исключений, чтобы в последующем передавать их между интерпретаторами. И также есть метод is_shareable
для проверки возможности разделять объекты.
Среди особенностей данного файла можно выделить безопасную работу с файлами. А также очистка состояний (module_clear
, traverse_module_state
), деаллокаторы (xibufferview_dealloc
), и использование Py_buffer
для работы с разделяемыми буферами.
Объекты интерпретаторов строго изолированы, существует переключение сессий через _PyXI_Enter
и _PyXI_Exit
. Ошибки изоляции также обрабатывается через unwrap_not_shareable
.
Конфигурация интерпретаторов совместима с PyInterpreterConfig
, а сам конфиг можно создать через метод new_config
.
Также интерпретаторы имеют управляемый жизненный цикл, реализуемый через проверку готовности, блокировки удаления текущего интерпретатора, счетчики ссылок.
Модуль помечен как Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
, что означает что отдельный GIL для каждого интепретатора поддерживается. Существуют специальные типы исключений (InterpreterError
, NotShareableError
). Совместимы с marshal
для сереализации сложных объектов.
Данный модуль отвечает за очередь обмена сообщениями между интерпретаторами. Очереди работают между интерпретаторами Python в одном процессе, используют глобальную память для хранения данных и поддерживают блокировки для синхронизации.
В этом модуле находится несколько структур.
Первая из них — это _queueitem
, элемент очереди, связный список.
struct _queueitem;
typedef struct _queueitem {
/* The interpreter that added the item to the queue.
The actual bound interpid is found in item->data.
This is necessary because item->data might be NULL,
meaning the interpreter has been destroyed. */
int64_t interpid;
_PyXIData_t *data;
unboundop_t unboundop;
struct _queueitem *next;
} _queueitem;
Принцип работы заключается в том, что каждый элемент очереди (_queueitem
) содержит:
interpid
: идентификатор-отправительdata
: буфер данных (до 256 КБ без сериализации)next
: указатель на следующий элемент (FIFO)
А также передача данных осуществляется только через queue.put
. Примитивы синхронизации заимствованы из threading.Lock
.
Следующая — это сама очередь (FIFO — first in — first out, первый вошел — первый вышел).
typedef struct _queue {
Py_ssize_t num_waiters; // protected by global lock
PyThread_type_lock mutex;
int alive;
struct _queueitems {
Py_ssize_t maxsize;
Py_ssize_t count;
_queueitem *first;
_queueitem *last;
} items;
struct _queuedefaults {
xidata_fallback_t fallback;
int unboundop;
} defaults;
} _queue;
Количество «ожидающих» (защищено GIL), мьютекс, статус жизни, подструктура _queueitems
с максимальным размером, числом, а также первым и последним элементом, подструктура _queuedefaults
для данных по умолчанию.
Потом идет _queueref
— ссылка на очередь:
struct _queueref;
typedef struct _queueref {
struct _queueref *next;
int64_t qid;
Py_ssize_t refcount;
_queue *queue;
} _queueref;
В ней находится объект следующей ссылки, ID очереди, количество ссылок и сама очередь в виде указателя.
И в конце структура _queues
:
typedef struct _queues {
PyThread_type_lock mutex;
_queueref *head;
int64_t count;
int64_t next_id;
} _queues;
Мьютекс, ссылка на очередь в виде «головы», количество и next_id
. _queues
является глобальным реестром всех очередей.
Также в этом модуле задается управление данными: _PyXIData_t
как контейнер для данных, и механизмы для сериализации и десериализации объектов. Кроме того, можно увидеть политику обработки «несвязанных» объектов, когда интерпретатор-источник уничтожен.
Модуль потокобезопасный (синхронизация идет через PyThread_type_lock
, операции с очередями атомарные). Используются собственные глобальные аллокаторы памяти, есть очистка и счетчики ссылок.
Ну и естественно обработка ошибок: python-исключения об очередях, система кодов и конвертация сишных ошибок в python-исключения.
Если кратко, то вот API модуля:
create()
/destroy()
— управление очередямиput()
/get()
— основные операцииbind()
/release()
— управление ссылкамиВспомогательные методы (
get_count()
,is_full()
и другие)
Modules/_interpchannelsmodule.c
Финальный модуль, набор примитивов. Этот код реализует низкоуровневый механизм межъядерных каналов для CPython, обеспечивающий передачу данных между интерпретаторами. Его ядром является структура globals
, обеспечивающая централизованное управление:
static struct globals {
PyMutex mutex; // Глобальный мьютекс для синхронизации
int module_count; // Счётчик активных под-интерпретаторов
_channels channels; // Корневой контейнер каналов
} _globals = {0};
Глобальное состояние защищено мьютексом, предотвращающим race conditions при доступе к списку каналов. Структура _channels
управляет жизненным циклом всех каналов процесса:
typedef struct _channels {
PyThread_type_lock mutex; // Мьютекс списка каналов
_channelref *head; // Связный список активных каналов
int64_t numopen; // Счётчик открытых каналов
int64_t next_id; // Генератор уникальных ID
} _channels;
Каждый канал представлен иерархией структур:
_channelref
— запись в глобальном реестре_channel_state
— основное состояние канала_channelitem
— элемент передачи данных
Элемент очереди сообщений инкапсулирует передаваемые данные и метаинформацию:
typedef struct _channelitem {
int64_t interpid; // ID интерпретатора-источника
_PyXIData_t *data; // Кросс-интерпретационные данные
_waiting_t *waiting; // Семафор синхронизации
unboundop_t unboundop; // Обработчик несвязанных объектов
struct _channelitem *next; // Следующий элемент
} _channelitem;
Ключевая структура _channel_state
управляет внутренним состоянием канала:
typedef struct _channel {
PyThread_type_lock mutex; // Локальный мьютекс
_channelqueue *queue; // Очередь сообщений (FIFO)
_channelends *ends; // Реестр интерпретаторов
struct {
unboundop_t unboundop; // Стандартный обработчик объектов
xidata_fallback_t fallback; // Fallback-сериализация
} defaults;
int open; // Флаг состояния
struct _channel_closing *closing; // Состояние закрытия
} _channel_state;
Особое внимание уделено двухфазному закрытию, fallback-сериализации и автоматическому разрешению ссылок. Все это вместе гарантирует безопасное завершение при параллельных операций, обработку объектов вне стандартного XI-формата.
Экспортируемый тип channelid
предоставляет интерфейс для Python:
typedef struct channelid {
PyObject_HEAD
int64_t cid; // Уникальный ID канала
int end; // Роль конечной точки
int resolve; // Флаг авторазрешения
_channels *channels; // Ссылка на контейнер
} channelid;
Как механизм, мьютексты иерархичны: из глобального в список каналов и дальше в локальный.
Операции атомарные (неделимые), существуют таумауты блокировок для предотвращения взаимных блокировок (извиняюсь за тавтологию).
И естественно не стоит забывать все это чистить — сборка мусора автоматическая при каждом уничтожении интерпретатора.
❯ Принцип работы передачи:
Отправка:
_PyXIData_t *data = xi_data_serialize(obj); // Сериализация
_channelitem *item = create_item(data); // Создание элемента
append_to_queue(queue, item); // Инъекция в очередь
signal_receivers(waiting); // Уведомление получателей
Получение:
_channelitem *item = pop_from_queue(queue); // Извлечение элемента
if (!item) wait_with_timeout(mutex, timeout); // Блокировка при пустой очереди
PyObject *obj = xi_data_deserialize(item->data); // Десериализация
Закрытие:
channel->open = 0; // Установка флага
broadcast_closing(channel->waiting); // Оповещение ждущих потоков
schedule_async_cleanup(channel); // Асинхронная очистка
Система обработки ошибок преобразует коды системных вызовов (например EAGAIN
) в Python-исключения, используя механизм PyErr_SetFromErrno
. Для критических секций применяется паттерн Py_BEGIN_CRITICAL_SECTION
с гарантией освобождения ресурсов. Реализация обеспечивает строгую изоляцию интерпретаторов через сериализацию объектов в независимое от GC представление.
❯ О модуле
Почитать об мотивации и о том как работает PEP можно по этой ссылке.
Модуль interpreters
доступен в Python 3.14, но само нахождения модуля изменено, теперь это concurrent.interpreters.
В нем можно найти следующие методы:
concurrent.interpreters.list_all()
— возвращает список объектов интерпретаторов, один для каждого известного.concurrent.interpreters.get_current()
— возвращает объект интерпретатора для текущего запущенного.concurrent.interpreters.get_main()
— возвращает объект интерпретатора для главного интепретатора.concurrent.interpreters.create()
— инициализирует новый (idle) Python-интерпретатор и возвращает объект интерпретаторе для него.
Подробнее об объектах можно почитать на странице документации.
Пример использования:
import concurrent.interpreters as interpreters
from textwrap import dedent
interp = interpreters.create()
# Run in the current OS thread.
interp.exec('print("spam!")')
interp.exec("""if True:
print('spam!')
""")
interp.exec(dedent("""
print('spam!')
"""))
def run():
print('spam!')
interp.call(run)
# Run in new OS thread.
t = interp.call_in_thread(run)
t.join()
Для Python 3.12+ есть еще PyPI-пакет interpreters-pep-734:
try:
import interpreters
except ModuleNotFoundError:
from interpreters_backports import interpreters
try:
import interpreters.queues
except ModuleNotFoundError:
import interpreters_backports.interpreters.queues
from interpreters_backports import interpreters
try:
from interpreters import channels
except ModuleNotFoundError:
from interpreters_experimental.interpreters import channels
try:
from concurrent.futures import ThreadPoolExecutor
except ModuleNotFoundError:
from interpreters_backports.concurrent.futures import ThreadPoolExecutor
❯ А подробнее?
Процесс один, но интерпретаторов несколько и у каждого свой GIL. Все они делят одну выделенную память. Для защиты от перетерания данных разными интерпретаторами используется pickle, он не допускается мутация одной памяти из разных источников (чтобы не конфликтовали интерпретаторы).
Применять их можно аналогично CSP из Golang (если реализовать шедулдер).
Про использование неизменяемых данных в субинтерпретаторах можно посмотреть в докладе Юрия Селиванова.
Также на PyCON US-24 презентовали функционал подинтепретаторов и free-threading. Видео-доклад можно посмотреть здесь.
Субинтерпретаторы не управляются ОС, существуют в одном процессе. Потоки ОС могут быть привязаны к разным интерпретаторам, и если интерпретатор использует свой GIL (PEP-684), его потоки не блокируются GIL других интерпретаторов.

Фактически, подинтерпретатор — это отдельное пространство имен, которое может иметь отдельный GIL. Изолированный от других подинтерпретаторов.
А может и не иметь:
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,
};
.gil = PyInterpreterConfig_OWN_GIL
может быть PyInterpreterConfig_SHARED_GIL
.
Согласно PEP:
The interpreters module will provide a high-level interface to the multiple interpreter functionality. The goal is to make the existing multiple-interpreters feature of CPython more easily accessible to Python code. This is particularly relevant now that CPython has a per-interpreter GIL (PEP 684) and people are more interested in using multiple interpreters.
Использование подинтепретаторов может дать буст к скорости благодаря отдельному GIL. И мы так плавно переходим к бенчмаркам.
❯ Бенчмарк
Код замера я взял отсюда (потребуется установка pyperf и httpx).
Бенчмарк использует IO-bound и CPU-bound задачи. Он запускает простую версию, Threading GIL/NoGIL, через мультипроцессинг и сами подинтерпретаторы.
IO-bound задача на Ryzen 7 5825u:
Regular: Mean +- std dev: 4.85 sec +- 0.48 sec
Threading: Mean +- std dev: 1.22 sec +- 0.19 sec
Multiprocessing: Mean +- std dev: 1.45 sec +- 0.26 sec
Subinterpreters: Meain +- std dev: 1.85 sec +- 0.30 sec
CPU-bound задача на Ryzen 7 5825u:
Regular: Mean +- std dev: 60.2 ms +- 0.6 ms
Threading: Mean +- std dev: 22.6 ms +- 0.7 ms
Multiprocessing: Mean +- std dev: 153 ms +- 3 ms
Subinterpreters: Mean +- std dev: 120.8 ms +- 4 ms
Для чистоты эксперимента, результаты на другой машине.
Здесь
WORKLOADS
были побольше чем в первом бенчмарке на ryzen, стали:WORKLOADS = [(1, 10000), (10001, 20000), (20001, 30000), (30001, 40000)]
CPU-bound задача на M2 Pro:
Regular: Mean +- std dev: 163 ms +- 1 ms
Threading with GIL: Mean +- std dev: 168 ms +- 2 ms
Threading NoGIL: Mean +- std dev: 48.7 ms +- 0.6 ms
Multiprocessing: Mean +- std dev: 73.4 ms +- 1.5 ms
Subinterpreters: Mean +- std dev: 44.8 ms +- 0.5 ms
IO-bound задача на M2 Pro:
Regular: Mean +- std dev: 1.45 sec +- 0.03 sec
Threading with GIL: Mean +- std dev: 384 ms +- 17 ms (~1/4 от 1.45s)
Threading NoGIL: Mean +- std dev: 373 ms +- 20 ms
Multiprocessing: Mean +- std dev: 687 ms +- 32 ms
Subinterpreters: Mean +- std dev: 547 ms +- 13 ms
Может показаться, что не так много производительности выдало. Но мы не учли что можно использовать асинхронность, многопоточность внутри подинтерпретаторов. В итоге отличный функционал для длинных задач, когда нужно использовать много или важна изоляция. Пишите свои мнения в комментариях.
Можно увидеть что сабинтерпретаторы уступают в IO-bound задачах перед тредингом. Почему так происходит?
В субинтерпретаторах каждый вызов interp.exec()
требует сериализировать данные, переключение сессий между интерпретаторами, создание нового GIL для каждой операции. Само создание интерпретаторов дорогое, и поэтому их лучше выбирать для CPU-bound — ибо они дают истинный параллелизм на нескольких ядрах.
Субинтерпретаторы показывают преимущество в CPU-bound задачах только при истинно параллельном выполнении (когда у каждого есть свой GIL). В текущем CPython (общий GIL) они проигрывают потокам из-за накладных расходов на создание и передачу данных.
Каждый вызов interp.exec()
требует преобразования данных в межъядерный формат через _PyXIData_t
. Для простых типов (int, str) это происходит быстро, но при передаче сложных объектов (словари, датаклассы) включается механизм сериализации, аналогичный pickle. В тестах с передачей 1000 словарей размером 1 КБ сериализация съедала 37% времени выполнения.
❯ Общие выводы
К просмотру рекомендую интервью CPython Core разработчика Никиты Соболева и разработчика модуля сабинтерпретаторов Эрика Сноу.
Погружаясь в историю и реализацию субинтерпретаторов, ясно одно — что это довольно фундаментальный сдвиг в архитектуре CPython. По сути можно достичь истинного паралеллизма через субинтерпретаторы.
Особенно интересует деталь что мы просто изолируем состояния интерпретаторов, выдавая каждому по своему GIL. В том интервью Эрик верно подметил, что «изоляция даёт нам концептуальное преимущество».
Но эта самая изоляция очень сложно далась — все из-за нюансов в виде:
Immortal objects (PEP 683): Объекты вроде None или малых целых чисел стали «бессмертными» — их счётчик ссылок фиксируется на астрономическом значении, исключая гонки между интерпретаторами.
Кстати, именно поэтому (из-за PEP 683)
sys.getrefcount(X)
где X — число от -5 до 256 включительно, показывает заоблачные значения, но стоит выйти за этот лимит, то число ссылок будет адекватным.
Статические типы: Проблема изменяемых атрибутов (dict, subclasses) решена через перенаправление запросов в per-interpreter хранилища.
Модули расширений: Требуют перехода на многофазную инициализацию (PEP 489) и heap-типы. Библиотеки вроде OpenSSL (через ssl модуль) — особый случай, где разделение состояния между интерпретаторами было проблематично. Но как известно, они уже побороли эту проблему.
Но не стоит забывать, что технология имеет свои минусы. Создание интерпретаторов весьма дорогое удовольствие — иногда легче обойтись потоками, многопроцессорностью или асинхронностью. Но все это вознаграждается, если уметь правильно использовать.
Особенно перспективна интеграция с asyncio. Каждый интерпретатор имеет свой собственный event loop, но пока что нет встроенной синхронизации между ними.
Субинтерпретаторы — не новая концепция. Их корни уходят в Python 1.5, где они возникли как ответ на проблему глобальных состояний. Идея инкапсуляции данных интерпретатора в отдельные структуры напоминает инженерные практики борьбы с «глобальным хаосом». Как отмечает Эрик, это логичное развитие: если потоки получили изолированные состояния (thread state), то и интерпретаторы заслужили аналогичное. Исторически вдохновением послужил TCL, но в Python эта функция десятилетиями оставалась «спящей» из-за недоступности из Python-кода и нарушений изоляции.
Эрик скептически относится насчёт массового использования субинтерпретаторов. В принципе, многие с ним согласятся, так как их ниша это библиотеки для высокоуровневых абстракций, веб-фреймворки, обработка данных. А также как альтернатива multiprocessing — ресурсы ОС при правильном использовании экономятся лучше, коммуникация в рамках процесса может быть быстрее.
Но успех технологии сабинтерпретаторов зависит также от адаптации C-расширений.
❯ Заключение
Код примеров и тестов работы с PEP-0734 доступен в моем репозитории.
Пользуясь правом небольшой рекламы, могу предложить вам подписаться на мой блог в телеграме и также на канал «Находки в опенсорсе». Если вам конечно статья понравилась и вы хотите видеть чуть больше.
Если вам понравилась статья, поделитесь ей с друзьями. А лучше приходите контрибьютить в python и прочие опенсорс проекты. Удачи!
Источники
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале ↩

Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
Комментарии (2)
avkritsky
16.07.2025 09:12Спасибо за статью!
Разве сабинтерпретаторы не потеряют актуальность с free-threads? как минимум общая область видимости/память, без необходимости сериализации данных между потоками
SystemSoft
exec: ДА КАК ВЫ СМЕЕТЕ.
eval: ну ладно, меня не заменят даже когда будет python 4.0.0.