【问题标题】:Memory leaks in Python when using an external C DLL使用外部 C DLL 时 Python 中的内存泄漏
【发布时间】:2014-11-03 16:00:53
【问题描述】:

我有一个 python 模块,它调用一个用 C 语言编写的 DLL 来编码 XML 字符串。一旦函数返回编码字符串,它就无法解除在此步骤中分配的内存。具体来说:

encodeMyString = ctypes.create_string_buffer(4096)

CallEncodingFuncInDLL(encodeMyString, InputXML)

我查看了thisthisthis 并尝试调用gc.collect,但也许由于该对象已在外部 DLL 中分配,python gc 没有任何记录它并且无法删除它。但由于代码不断调用编码函数,它不断分配内存,最终python进程崩溃。有没有办法分析这种内存使用情况?

【问题讨论】:

  • Python 无法跟踪它不知道的外部事物分配的内存;你必须手动完成。大概 DLL 有一个ReleaseThingEncodedByDLL 函数。你必须这样称呼。将其中的值复制到适当的本机 Python 对象并立即释放它,或者将其包装在一个对象中,然后在 close__exit____del__ 或任何适当的地方释放它。
  • @abarnert,但是 python 的进程本身正在跟踪这个内存,它只是无法删除它。这怎么可能?
  • 你是什么意思“python的进程本身正在跟踪这个内存”?如果您的意思是它在 python 进程的堆中……是的,当然是,DLL 是进程的一部分,并加载到它的堆中。这并不意味着 Python 可以跟踪 DLL 分配的任何内存,更不用说知道该 DLL 何时使用它。

标签: python c memory memory-leaks


【解决方案1】:

由于您没有提供有关 DLL 的任何信息,这肯定会非常模糊,但是……

Python 无法跟踪它不知道的外部事物分配的内存。怎么可能?该内存可能是 DLL 的常量段的一部分,或者分配有 mmapVirtualAlloc,或者是更大对象的一部分,或者 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-15
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    • 2020-05-13
    • 2016-06-11
    • 1970-01-01
    相关资源
    最近更新 更多