eli*_*liu 8 python garbage-collection loops multiprocessing
请注意这个简单的代码:
import random
while True:
L = list( str(random.random()))
Run Code Online (Sandbox Code Playgroud)
问题:如果我让它运行,python 会耗尽内存吗?
我问的原因:
此循环的第一次迭代,创建了一个列表,并分配了 'L' 来表示该列表。此循环的下一次迭代,创建另一个列表,从前一个列表中拉出“L”并分配给新列表。之前的列表已经失去了它的参考。之前的列表会被垃圾收集吗?如果不是在每次迭代结束时,但最终我希望?
话虽如此,只需将场景进一步扩展到多处理:
import random
while True:
l1 = list( str(random.random()))
pseudo: multiprocessing.Queue.put(l1)
# how is l1 handled here?
# is l1 .copy()-ed to the queue or referenced by the queue?
# is l1 destoryed in this process (this while loop) at the end of iteration?
Run Code Online (Sandbox Code Playgroud)
kin*_*all 11
垃圾收集的主要方式是 CPython(该语言的引用实现)中的引用计数。当不再有对对象的任何引用时,它占用的内存会立即释放并可供其他 Python 对象重用。(它可能会也可能不会被释放回操作系统。)有一些永远不会被释放的对象的例外:小整数、内部字符串(包括文字)、空元组、None
.
因此,要回答您最初的问题,L
将在每次迭代时重新分配给一个新列表。此时,前面的列表没有引用,它的内存将立即释放。
关于您的第二个示例,将某些内容放入multiprocessing
队列是必要的复制操作。对象必须被序列化(Python 术语中的“pickled”)才能发送到新进程,新进程有自己的内存空间,无法从原始进程的内存中看到任何内容。当您在循环中重新分配li
给下一个列表时,前一个列表没有引用,并且将再次被释放。
在循环结束时,L
orl1
变量仍然引用一个列表:您在循环的最后一次迭代中创建的列表。如果你想释放这个对象,只是del L
或del l1
分别。
PS——当对象包含对自身的引用(直接或间接通过其他对象链)时,这被称为循环引用。这些不是通过引用计数自动收集的,Python 有一个单独的垃圾收集器,它会定期运行以清理它们。
我们可以通过向__del__
类添加自定义命令来轻松测试这一点,以观察会发生什么:
class WithDestructor(object):
def __del__(self):
print(f"Exploding {self}")
Q=None
for i in range(5):
Q = WithDestructor()
print(f"In loop {i}")
Run Code Online (Sandbox Code Playgroud)
如果清理只发生在循环结束时,我们会得到循环输出,然后是析构函数输出。相反,我将它隔行扫描,因此在重新分配Q
时Q
会立即清理对象。
In loop 0
Exploding <__main__.WithDestructor object at 0x7f93141176d8>
In loop 1
Exploding <__main__.WithDestructor object at 0x7f93141172b0>
In loop 2
Exploding <__main__.WithDestructor object at 0x7f93141176d8>
In loop 3
Exploding <__main__.WithDestructor object at 0x7f93141172b0>
In loop 4
Run Code Online (Sandbox Code Playgroud)