本文讨论的话题涉及python的容器,内存管理和深浅拷贝
考虑一个任务
从另一个系统中我们会的得到一批数据,我们对它进行某种 X 操作,然后放到队列中 q2 中,等待存储例程将这些消息入库
这个任务升级,有一定概率我们需要对消息进行 X 的逆操作,具体来说就是,当触发逆操作时,我们需要从之前做过 X 操作的message 取一条,修改message的一个属性,然后将该message执行逆操作,并且把新的message 存入队列 q2 中等待入库
一个存储任务 save_task ,负责将处理记录写入db进行持久存储 ,它负责不断从 q2 中取出message 入库
我们容易做一个样的实现:
- 构建一个 buffer 队列,把进行过 X 操作的message 放到buffer中, 如果触发了逆操作,我们就从中哪一个message出来取逆,处理完以后交给 q2.
如图
![](https://img.haomeiwen.com/i4722219/916eb02b644dadb2.png)
这个任务容易出现一种怪现象: 入库数据 的逆操作数据总出现重复记录
示例代码
psudo code
from queue import Queue
buffer = Queue()
q2 = Queue()
if random.randomint(a, b) < a:
do_x(message)
buffer.put(message)
q2.put(message)
elif not buffer.empty():
msg = buffer.get()
do_inverse_x(msg)
msg.status = 2
msg.trace_id = "inverse_" + msg.trace_id
q2.put(msg)
save_task()
原因是buffer 和 q1 的对象默认情况下是共享内存的, 出发逆操作后,buffer的数据取出并被修改,直接影响了q2中尚未被处理的对象内存。
举例子而言。
q1 今有 [a, b, c] 三条数据
随机产生 1,从队尾拿到 c,进行 X 工序后,原封不动放到 buffer ,同时 c 放到q2;
再次随机产生 0 , 我们要在buffer里去一条数据做逆操作,取出 c ,我们将它改成 C 做完逆操作后,交给 q2;
修改数据时,q2的 c也被改成了 C,C再次被添加到 q2时,q2中就有了两份数据一模一样但对象id的数据。
因此就会看到数据库总是会出现两条逆操作的数据,一模一样。
本质是一个副本两处存储可变对象惹的祸
Python的多数容器存储对象时,为了节省内存都是默认存储对象的引用,而非内存,因此一旦一个对象你要修改挪作他用的时候,最后的办法是用 copy.copy() 全新地复刻一个新对象出来,而不是直接在可变对象上直接修改。
总结
当一个对象存储在两个容器时,写操作容易引起一个联动的变化——两处内存同时被修改。这一般不是期望的,因此,一般我们准备要作写操作时,要进行深拷贝,不直接使用赋值 “=”,而是用 copy模块里的copy方法
网友评论