使用+ =运算符时列表的托管字典在多处理中未更新

时间:2019-05-29 15:34:56

标签: python-3.x list dictionary python-multiprocessing multiprocessing-manager

考虑以下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)使用element wise方法更新列表是可行的,但是比较麻烦,是否有任何建议使其更干净/更快?

1 个答案:

答案 0 :(得分:1)

我相信您遇到的问题与您用来创建字典的匿名dic1对象在字典Manager中检测到更改有关。

使用+=运算符更改列表本身不会更改对该列表的 reference -它是相同的列表,只是其中的一个元素已更改(即,第0个元素存储在线程安全字典dic1中的键'1'下的列表。

对于dic2,情况有所不同。与以下行:

self.dic2['1'] = [a[0]+1, a[1], a[2]]

您有效地更新密钥'1'下存储的值。分配的值是全新列表。它由存储在相同键下的先前值的列表元素组成,但仍然是不同列表。

Manager对象检测到此类更改,并且在检查dic2值的过程中对引用进行了无缝更新,以便您可以读取正确的值。

这里的要点如下:

如果键,值或两者均未更改,则线程安全集合(dict)不会将任何更改传播到其他进程(或线程)。列表是一种引用类型,因此即使列表值更改,值(即引用)也不会更改。