使用 += 运算符时,列表的托管字典未在多处理中更新
Managed dict of list not updated in multiprocessing when using += operator
考虑以下 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
属性的托管字典。目标是在多处理环境中增加这个列表的一些元素,但是有些方法并没有有效地修改列表。
方法一:dictSumOverloaded
重载运算符 +=
用于将列表的元素递增 1,但结果不会持续存在。字典未更新。
方法二:dictSumElementWise
此函数基于旧列表和要添加的值明智地创建一个新的列表元素。然后将新列表分配给字典键。字典修改成功
健全性检查:在多处理环境之外
dic3
在多处理环境外使用 +=
时按预期修改。
问题:
1) 为什么 +=
不修改多处理环境中的列表元素?
2) 使用元素明智的方法来更新列表,但很麻烦,有什么建议吗cleaner/faster?
我相信您遇到的问题与检测到字典 dic1
中的更改有关,您创建它的匿名 Manager
对象。
使用 +=
运算符更改列表本身不会将 reference 更改为列表 - 它是同一个列表,只是其中的一个元素发生了变化(即存储在 thread-safe 字典 dic1
中键 '1'
) 下的列表的第 0 个元素。
与dic2
情况不同。使用以下行:
self.dic2['1'] = [a[0]+1, a[1], a[2]]
您实际上更新 存储在键 '1'
下的值。分配的值是一个 全新的 列表。它由存储为同一键下的先前值的列表元素组成,但它仍然是一个 不同 列表。
这样的变化被 Manager
对象检测到,并且在您检查 dic2
值的过程中的引用被无缝更新,以便您可以读取正确的值。
这里的重点是:
如果键和/或值没有更改,thread-safe 集合 (dict
) 不会将任何更改传播到其他进程(或线程)。列表是引用类型,因此即使列表值发生变化,值(即引用)也不会改变。
考虑以下 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
属性的托管字典。目标是在多处理环境中增加这个列表的一些元素,但是有些方法并没有有效地修改列表。
方法一:dictSumOverloaded
重载运算符 +=
用于将列表的元素递增 1,但结果不会持续存在。字典未更新。
方法二:dictSumElementWise
此函数基于旧列表和要添加的值明智地创建一个新的列表元素。然后将新列表分配给字典键。字典修改成功
健全性检查:在多处理环境之外
dic3
在多处理环境外使用 +=
时按预期修改。
问题:
1) 为什么 +=
不修改多处理环境中的列表元素?
2) 使用元素明智的方法来更新列表,但很麻烦,有什么建议吗cleaner/faster?
我相信您遇到的问题与检测到字典 dic1
中的更改有关,您创建它的匿名 Manager
对象。
使用 +=
运算符更改列表本身不会将 reference 更改为列表 - 它是同一个列表,只是其中的一个元素发生了变化(即存储在 thread-safe 字典 dic1
中键 '1'
) 下的列表的第 0 个元素。
与dic2
情况不同。使用以下行:
self.dic2['1'] = [a[0]+1, a[1], a[2]]
您实际上更新 存储在键 '1'
下的值。分配的值是一个 全新的 列表。它由存储为同一键下的先前值的列表元素组成,但它仍然是一个 不同 列表。
这样的变化被 Manager
对象检测到,并且在您检查 dic2
值的过程中的引用被无缝更新,以便您可以读取正确的值。
这里的重点是:
如果键和/或值没有更改,thread-safe 集合 (dict
) 不会将任何更改传播到其他进程(或线程)。列表是引用类型,因此即使列表值发生变化,值(即引用)也不会改变。