Еще одной важной часть субинтерпретаторов являются очереди. Прочитать про них можно тут:
- https://peps.python.org/pep-0734/#queue-objects
- https://github.com/python/cpython/blob/main/Lib/concurrent/interpreters/_queues.py
- https://github.com/python/cpython/blob/main/Modules/_interpqueuesmodule.c
Зачем нужны очереди? Чтобы передавать объекты между интерпретаторами:
>>> from concurrent import interpreters
>>> interp = interpreters.create()
>>> queue = interpreters.create_queue()
>>> complex_object = [1, 'a', {'key': 'val'}, (2, 3), None, object()]
>>> complex_object # notice original id
[1, 'a', {'key': 'val'}, (2, 3), None, <object object at 0x101169ff0>]
>>> queue.put_nowait(complex_object)
>>> interp.prepare_main(queue=queue) # passing `queue` object to other interpreter
>>> interp.exec('print(queue.get_nowait())') # notice id change
[1, 'a', {'key': 'val'}, (2, 3), None, <object object at 0x101b054e0>]
В чем важные особенности?
- Не все объекты переданные между очередями копируются. Например, immortal objects - не копируются:
>>> id(12)
4308538240
>>> interp.exec('print(id(12))')
4308538240
-
memoryview (как и другие PyBuffer объекты) имеет особые гарантии. Мы передаем память прям физически. Без копирования. Потому что np.array и подобное - тоже буферы. Если бы мы копировали память буферов, то такие вычисления не отличались бы от multiprocessing, который часть по памяти имеет проблемы. Здесь – все по-другому:
>>> b = bytearray(b'123')
>>> m = memoryview(b)
>>> queue.put_nowait(m)
>>> interp.exec('(m := queue.get_nowait()); print(m); m[:] = b"456"') # changing memory directly
<memory at 0x103274940>
>>> b # was changed in another interpreter!
bytearray(b'456')
Будьте осторожны с мутабельными буферами. Тут нужно понимать, что ты делаешь. Инструмент - очень мощный.
Больше подробностей будет в моем интервью с автором субинтерпретаторов.
- Остальные объекты копируются через
pickle (пока что). В планах – ускорить и упростить данный процесс:
# ex.py
>>> class Custom:
... def __init__(self, arg: int) -> None:
... self.arg = arg
... def __getstate__(self):
... print('__getstate__')
... return {'arg': self.arg}
... def __setstate__(self, state):
... print('__setstate__', state)
... self.arg = state['arg']
# main.py
>>> from ex import Custom
>>> c = Custom(1)
>>> c
<ex.Custom object at 0x101a078c0>
>>> queue.put_nowait(c)
__getstate__
>>> interp.exec('print(queue.get_nowait())') # different object
__setstate__ {'arg': 1}
<ex.Custom object at 0x103222f20>
А еще есть каналы! CSP, прям как в Go: https://peps.python.org/pep-0554/#channels
Но, они пока готовы сильно меньше, чем очереди, которые уже достаточно полезные.
Но, самое главное: можно делать свои модули на C, которые будут копировать данные так, как вам нужно (или не копировать их вовсе). АПИ готовы, они работают.
Круто?
Минутка монетизации опенсорса
Часовое интервью на английском с русскими субтитрами с Eric Snow – автором субинтерпретаторов – уже на бусти. Дополнительно я вставил все примеры кода и отсылки на ПЕПы прямо в видео, чтобы было удобно смотреть и понимать, о чем мы говорим. Через неделю оно будет доступно для всех на ютюбе.
Если вы хотите поддерживать глубокий бесплатный технический контент, уникальных гостей на интервью, а так же другую мою бесплатную работу: от опенсорса до статей здесь и на хабре – есть бусти!
А еще я буду там выкладывать список ссылок на весь другой контент, который я делаю. В одном месте. Удобно!
Обсуждение: какие у вас есть варианты использования субинтерпретаторов?
| Поддержать | YouTube | GitHub | Чат |