在使用外部C DLL时Python的内存泄漏
我有一个Python模块,它调用一个用C语言写的DLL来编码XML字符串。当这个函数返回编码后的字符串时,它没有释放在这个过程中分配的内存。具体来说:
encodeMyString = ctypes.create_string_buffer(4096)
CallEncodingFuncInDLL(encodeMyString, InputXML)
我查看了这个、这个和这个,还尝试过调用gc.collect
,但可能因为这个对象是在外部DLL中分配的,Python的垃圾回收机制并没有记录到它,所以无法释放它。由于代码不断调用编码函数,它就不断分配内存,最终导致Python进程崩溃。有没有办法来监控这个内存使用情况?
1 个回答
因为你没有提供关于DLL的任何信息,所以我只能说得比较模糊,但……
Python无法追踪那些它不知道的外部分配的内存。怎么可能呢?那块内存可能是DLL的常量部分,或者是用mmap
或VirtualAlloc
分配的,或者是更大对象的一部分,或者DLL可能只是希望它在使用时是活着的。
任何一个有函数分配并返回新对象的DLL,都必须有一个函数来释放那个对象。例如,如果CallEncodingFuncInDLL
返回一个你需要负责的新对象,那么就会有一个像DestroyEncodedThingInDLL
这样的函数,它会接收这个对象并释放它。
那么,你什么时候调用这个函数呢?
让我们退一步,把这个说得更具体一点。假设这个函数是普通的strdup
,那么你用来释放内存的函数就是free
。你有两个选择,决定什么时候调用free
。我不知道你为什么会想从Python调用strdup
,但这是最简单的例子,所以我们就假装它不是没用的。
第一个选择是调用strdup
,立即将返回的值转换为原生的Python对象,然后释放它,之后就不用再担心了:
newbuf = libc.strdup(mybuf)
s = newbuf.value
libc.free(newbuf)
# now use s, which is just a Python bytes object, so it's GC-able
或者,更好的是,使用自定义的restype
可调用对象来自动处理:
def convert_and_free_char_p(char_p):
try:
return char_p.value
finally:
libc.free(char_p)
libc.strdup.restype = convert_and_free_char_p
s = libc.strdup(mybuf)
# now use s
但是,有些对象不能那么容易地转换为原生的Python对象——或者可以,但这样做并没有太大意义,因为你需要不断地将它们传回DLL。在这种情况下,你不能在用完之前就清理它。
最好的方法是把那个不透明的值封装在一个类里,在close
、__exit__
或__del__
等适当的时候释放它。一个不错的做法是使用@contextmanager
:
@contextlib.contextmanager
def freeing(value):
try:
yield value
finally:
libc.free(value)
所以:
newbuf = libc.strdup(mybuf)
with freeing(newbuf):
do_stuff(newbuf)
do_more_stuff(newbuf)
# automatically freed before you get here
# (or even if you don't, because of an exception/return/etc.)
或者:
@contextlib.contextmanager
def strduping(buf):
value = libc.strdup(buf)
try:
yield value
finally:
libc.free(value)
现在:
with strduping(mybuf) as newbuf:
do_stuff(newbuf)
do_more_stuff(newbuf)
# again, automatically freed here