工作中踩到的一个坑,发现多进程之间共享字典变量的时候, 无法深层赋值。很有意思的一个现象,可能是python多进程间的一种变量保护机制。不过没有任何异常报错,就有点坑了。
一 共享变量dict
我们在ipython交互环境中演示:
1
2
3
4
5
6
>>> import multiprocessing
>>> m = multiprocessing.Manager()
>>> d = m.dict() # 创建共享字典变量
>>> d
<DictProxy object, typeid 'dict' at 0x7fe211518d68>
d[‘a’]赋值常量
1
2
3
>>> d['a'] = 1 # 赋值常量
>>> d['a']
1
d[‘b’]赋值可变变量
1
2
3
>>> d['b'] = [1,2,3]
>>> d['b']
[1, 2, 3]
给d[‘b’]深层赋值,可以看到append失败,d[‘b’]的值没有改变
1
2
3
>>> d['b'].append(4) # 深层赋值
>>> d['b']
[1, 2, 3] # 没有改变,深层赋值失败
将d[‘b’]赋值给x1, 查看d[‘b’]的id和x1的id,可以发现d[‘b’]的id与x1的id不一样,说明x1 = d[‘b’]是deepcopy,而不是简单的引用
1
2
3
4
5
6
7
>>> x1 = d['b']
[1, 2, 3]
>>> id(d['b'])
140691592776072
>>> id(x1)
140691592775944
查看d[‘b’]的类型,与x1一样,是普通的list。x1追加5,成功。d[‘b’]再次尝试深层赋值失败, d[‘b’]与x1都是list,赋值结果却大不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> type(d['b'])
<class 'list'>
>>> type(x1)
<class 'list'>
>>> x1.append(5)
>>> x1
[1, 2, 3, 5]
>>> d['b'].append(5)
>>> d['b']
[1, 2, 3]
二 共享变量list
继续尝试list类型的共享变量, 可以看到同样无法深层赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> l = m.list()
>>> l
<ListProxy object, typeid 'list' at 0x7ff54f5571d0>
>>> l.append(1)
>>> l.append(2)
>>> l.append(x1)
>>> for i in l:
... print(i)
...
1
2
[1, 2, 3, 5]
>>> l[2].append(6) # 深层赋值失败
>>> l[2]
[1, 2, 3, 5]
三 共享变量之间深层赋值
突然想到多进程间的list, dict,已经与普通的dict, list不是一种类型了。如果我们在共享变量上赋值普通变量,python多进程间通信肯定要控制变量的改变。也就不会允许在共享变量中对普通变量深层赋值。
尝试将list共享变量l,赋值给dict共享变量d, 可以看到赋值成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
d['c'] = l
>>> d['c'][2]
[1, 2, 3, 5]
>>> for i in d['c']:
... print(i)
...
1
2
[1, 2, 3, 5]
>>> for i in l:
... print(i)
...
1
2
[1, 2, 3, 5]
看到虽然赋值成功了, 但d[‘c’]与l并不是同一个值,id与内存地址都不同
1
2
3
4
5
6
7
8
9
>>> d['c']
<ListProxy object, typeid 'list' at 0x7ff54f557f28>
>>> l
<ListProxy object, typeid 'list' at 0x7ff54f5571d0>
>>> id(d['c'])
140691574718136
>>> id(l)
140691574714832
继续深层赋值,我们看到给d赋值, l的值也改变了,同样l赋值,d的值也同步变化,深层赋值成功了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> d.append(6)
>>> for i in l:
... print(i)
...
1
2
[1, 2, 3, 5]
6
7
>>> l.append(7)
>>> for i in d['c']:
... print(i)
...
1
2
[1, 2, 3, 5]
6
7
四 结论
Manager创建的多进程共享变量,所赋值的普通变量(list, dict)都是不可在改变,如果想赋值可变变量,必须使用manager类型的变量(manager.list, manager.dict等