Рассмотрим следующий код Python:
from multiprocessing import Process, Manager
class MyClass():
def __init__(self, dic1, dic2):
self.dic1 = Manager().dict(dic1) # Create a managed dictionary
self.dic2 = Manager().dict(dic2) # Create a managed dictionary
process1 = Process(target=self.dictSumOverloaded, args=())
process2 = Process(target=self.dictSumElementWise, args=())
process1.start()
process1.join()
process2.start()
process2.join()
def dictSumOverloaded(self):
self.dic1['1'][0] += 1 # dic1 is not updated
def dictSumElementWise(self):
a = self.dic2['1']
self.dic2['1'] = [a[0]+1, a[1], a[2]] # dic2 is updated
def main():
dic1 = {'1': [1, 0, 0]}
dic2 = {'1': [1, 0, 0]}
result = MyClass(dic1, dic2)
print(result.dic1) # Failed
print(result.dic2) # Success
# Bypass multiprocessing environment
dic3 = {'1': [1, 0, 0]}
dic3['1'][0]+=1
print(dic3) # Success
if __name__ == '__main__':
main()
В этом примере я создаю управляемый словарь, содержащий список в качестве атрибута MyClass
. Цель состоит в том, чтобы увеличить некоторые элементы этого списка в многопроцессорной среде, но некоторые методы не изменяют список эффективно.
Способ 1:dictSumOverloaded
Перегруженный оператор +=
используется для увеличения элемента списка на 1, но результат не сохраняется. Диктант не обновляется.
Способ 2:dictSumElementWise
Эта функция создает новый элемент списка на основе старого списка и добавляемых значений. Затем новый список назначается клавише dict. Диктант успешно изменен.
Санитарная проверка: вне многопроцессорной средыdic3
изменяется, как и ожидалось, при использовании +=
вне многопроцессорной среды.
Вопросы:
1) Почему +=
не изменяет элемент списка в многопроцессорной среде?
2) Использование поэлементного метода для обновления списка работает, но громоздко, какие-либо предложения по тому, как сделать его чище/быстрее?
Я считаю, что проблема, с которой вы столкнулись, связана с обнаружением изменения в словаре dic1
анонимным Manager
объектом, с которым вы его создаете.
Изменение самого списка с помощью оператора +=
не меняет ссылка на список - это тот же список, просто изменился его элемент (а именно 0-й элемент списка, хранящегося в потокобезопасном словаре dic1
под ключом '1'
).
С dic2
ситуация иная. Со следующей строкой:
self.dic2['1'] = [a[0]+1, a[1], a[2]]
Вы эффективно Обновить значение, хранящееся под ключом '1'
. Присвоенное значение представляет собой список совершенно новый. Он состоит из элементов списка, хранящихся как предыдущее значение под тем же ключом, но, тем не менее, это список разные.
Такое изменение обнаруживается объектом Manager
, и ссылка в процессе проверки значения dic2
плавно обновляется, чтобы вы могли прочитать правильное значение.
Главное здесь заключается в следующем:
потокобезопасная коллекция (dict
) не распространяет никаких изменений на другие процессы (или потоки), если нет изменений в ключах или значениях, или в том и другом. Список является ссылочным типом, поэтому значение (т.е. ссылка) не изменяется, даже если значения списка изменяются.
Подождите: документ 3.7 говорит Changed in version 3.6: Shared objects are capable of being nested. For example, a shared container object such as a shared list can contain other shared objects which will all be managed and synchronized by the SyncManager.
Значит ли это, что мой код будет правильным в версии 3.6+? Конечно, я только что проверил и использую Python 3.5.6.
Я считаю, что это сработает, только если список внутри словаря будет создан как Manager().list
.
Может быть, я не пробовал. Если я когда-нибудь попытаюсь использовать +=
в версии 3.6+ или использовать управляемый список внутри управляемого словаря, я постараюсь не забыть обновить этот комментарий!
Спасибо за разъяснение, жаль, что я не понял этого раньше. Является ли мой метод dic2 лучшим способом в этом случае? Я не нашел лучшего/быстрого способа сохранить три значения в этом управляемом словаре и увеличить их.