Python Передача объектов между процессами
В моём проекте есть несколько объектов, у которых есть метод parse(data) принимающий на вход словарь, производящий математические вычисления с элементами словаря и раскидывающий результаты по полям объекта. Встал вопрос о распараллеливании. Асинхронизация и разделение на потоки не имеет смысла, ибо операции завязаны на процессор и следовательно нужно раскидывать по ядрам. Вылезла проблема общей памяти. То есть, мне нужно передать в дочерний процесс объект и словарь и получить назад в родительский обработанный объект. И вот незадача: внутри дочернего процесса данные корректно обрабатываются и поля заполняются. Однако в возвращенном объекте изменены только поля содержащие примитивные типы. Списки, словари и прочее остается без изменений. Код:
import multiprocessing as mp
class Container:
value = 0
array = [0, 1, 2]
pairs = {0: 0, 1: 1, 2: 2}
def foo(data):
obj, value = data
obj.value = value
obj.array[1] = value
obj.pairs[1] = value
log('Inside:',obj)
return obj
def log(prefix: str, container: Container):
print(prefix, container.value, container.array, container.pairs)
if __name__ == "__main__":
container = Container()
log('Before:', container)
with mp.Pool(processes=1) as pool:
result = list(pool.map(foo, [(container, 5)]))
log('After:', result.pop())
Вывод:
Before: 0 [0, 1, 2] {0: 0, 1: 1, 2: 2}
Inside: 5 [0, 5, 2] {0: 0, 1: 5, 2: 2}
After: 5 [0, 1, 2] {0: 0, 1: 1, 2: 2}
Пробовал использовать Queue, но эффект тот же. Если кто-то сталкивался с таким же, и знает решение, подскажите пожалуйста, как передавать и изменять объекты между процессами?
Ответы (2 шт):
Вот так работает через BaseManager, но нужно много методов обвязки для класса писать - геттеры, сеттеры, плюс менеджер создавать и в нём класс регистрировать. Наверняка как-то проще уже давно можно, я на старом ответе на английском СО базировался:
import multiprocessing as mp
from multiprocessing.managers import BaseManager
class Container(object):
def __init__(self):
self.value = 0
self.array = [0, 1, 2]
self.pairs = {0: 0, 1: 1, 2: 2}
def set_value(self, value):
self.value = value
def set_array(self, index, value):
self.array[index] = value
def set_pairs(self, index, value):
self.pairs[index] = value
def get_value(self):
return self.value
def get_array(self):
return self.array
def get_pairs(self):
return self.pairs
class ContainerManager(BaseManager):
pass
def foo(data):
obj, value = data
obj.set_value(value)
obj.set_array(1, value)
obj.set_pairs(1, value)
log('Inside:',obj)
return obj
def log(prefix: str, container: Container):
print(prefix, container.get_value(), container.get_array(), container.get_pairs())
if __name__ == "__main__":
ContainerManager.register('Container', Container, exposed = ['set_value', 'set_array', 'set_pairs', 'get_value', 'get_array', 'get_pairs'])
containerManager = ContainerManager()
containerManager.start()
container = containerManager.Container()
log('Before:', container)
with mp.Pool(processes=1) as pool:
result = list(pool.map(foo, [(container, 5)]))
log('After:', result.pop())
Вывод:
Before: 0 [0, 1, 2] {0: 0, 1: 1, 2: 2}
Inside: 5 [0, 5, 2] {0: 0, 1: 5, 2: 2}
After: 5 [0, 5, 2] {0: 0, 1: 5, 2: 2}
Всё оказалось намного проще. Достаточно инициализировать поля в экземпляре класса, а не в самом классе, спасибо @user207200 за идею:
def __init__(self):
self.value = 0
self.array = [0, 1, 2]
self.pairs = {0: 0, 1: 1, 2: 2}
Вывод:
Before: 0 [0, 1, 2] {0: 0, 1: 1, 2: 2}
Inside: 5 [0, 5, 2] {0: 0, 1: 5, 2: 2}
After: 5 [0, 5, 2] {0: 0, 1: 5, 2: 2}
Теперь, почему это работает. При передаче объектов в другой процесс и при получении результата обратно объекты сериализуются и десериализуются. Для того, чтобы это могло делаться автоматически, у экземпляра класса должны быть видны все нужные поля. И вот в этом проблема - при десериализации питоном автоматически создаётся экземпляр класса Container, но у него ещё нет никаких своих полей, поэтому питон не понимает, куда ему копировать те поля экземпляра класса, которые вы выставили в методе foo. А вот если сделать нормальный инициализатор класса, эти поля у экземпляра класса появляются сразу при создании экземпляра и десериализация тогда проходит успешно.
Небольшое дополнительное разъяснение. В новый процесс отправляется копия объекта, которая для этого сериализуется при отправке её в новый процесс и при получении результата из этого процесса. При получении создаётся новый объект, в который копируются поля объекта после десериализации. В результате тут получается сразу три объекта:
- исходный объект (сериализованный и переданный в новый процесс)
- объект, автоматически созданный в новом процессе (в который десериализуется исходный объект)
- объект, автоматически созданный после получения результата (куда десериализуется объект из нового процесса)
Ещё пояснение. При создании экземпляра класса он получает как бы копии полей класса. После изменения полей экземпляра класса эти поля начинают жить своей отдельной от класса жизнью. Это довольно тонкая тема, возможно я и сам её не до конца точно понимаю.