- Ссылка на черновик PEP: https://github.com/python/peps/pull/4497
- Оригинальный PEP-683 с Immortal объектами: https://peps.python.org/pep-0683
Продолжаем про будущее субинтерпретаторов! Да, что-то меня увлекла данная тема :)
Как делиться объектами без копирования данных в субинтерпретаторах?
Чтобы легко делиться объектами между субинтерпретаторами, необходимо, чтобы они были полностью иммутабельными. Полностью. Не с точки зрения питона, а с точки зрения C.
Даже если вы будете делиться объектами, которые внешне нельзя изменить, то все еще будет один очень важный нюанс.
Напомню, что объект питона с точки зрения C выглядит так (упрощенно):
struct PyObject {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
};
И вот тот самый
ob_refcnt все время меняется. Буквально в любом C коде все время вызовы Py_DECREF и Py_INCREF. Без изменения счетчика ссылок невозможно выполнить никакой код. И буквально все части питона содержат вызовы, которые уменьшают или увеличивают счетчик ссылок во время работы программы. Следовательно, мы можем словить гонку данных.
Чтобы гонки не было, мы не должны менять
ob_refcnt. А чтобы его не менять, нам нужно, чтобы объект был бессмертным: жил на протяжении всего времени жизни программы и потом был правильно собран.Что такое вообще Immortal объекты?
Ровно как я и писал выше – объекты, которые не участвуют в подсчете ссылок и живут, пока программа не завершится. Что-то вроде
static в C и 'static в Rust.Внутри питона есть несколько мест, которые нам интересны с данной точки зрения:
#define _Py_IMMORTAL_REFCNT_LOCAL UINT32_MAX
То есть – объект
Immortal, когда у него просто очень большой refcnt. Что легко проверить:
>>> import sys
>>> sys.getrefcount(None)
3221225472
>>> x = None # он не будет меняться при создании новых имен:
>>> sys.getrefcount(None)
3221225472
И вот почему:
static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{
if (_Py_IsImmortal(op)) {
_Py_INCREF_IMMORTAL_STAT_INC();
return;
}
op->ob_refcnt++;
}
static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
{
if (_Py_IsImmortal(op)) {
return;
}
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
}
Когда объект "immortal", то с ним и не нужно проводить никаких доп операций.
Как предлагается делать свои?
import sys
admin = User('admin')
sys._immortalize(admin)
assert sys._is_immortal(admin)
Все! Теперь объект
admin будет жить до конца программы. И его можно будет использовать в разных субинтерпретаторах без копирования:
>>> from concurrent import interpreters
>>> interp = interpreters.create()
>>> interp.prepare_main(admin=admin)
>>> interp.exec('print(admin)')
'<User: admin>'
Поверх такого АПИ, скорее всего, будет какое-то красивое АПИ. Нужно будет следить, чтобы бессмертные объекты не были слишком большими / их не было слишком много. Ну и подготовить их логически тоже необходимо.
Итого: иммутабельные объекты, субинтерпретаторы, полная параллельность. Да у нас, что, Erlang?!
Обсуждение: как вам данная фича?
| Поддержать | YouTube | GitHub | Чат |