如何解决在Python中使用供应商的DLL文件时出现的内存泄漏问题?

4
我正在使用Python的ctypes模块通过加载供应商的库来使用与业务软件相关的C API。但是,在部署我编写的软件后,我发现供应商的库会根据其API中某个特定函数的调用次数而持续和可预测地泄漏内存。即使我在不使用堆分配的C程序中复制了这个泄漏问题。我联系了供应商关于这个问题,他们回答说正在解决,但我可能无法真正期望在下一个软件版本之前修复它。我想过当泄漏函数的调用次数达到一定阈值时重新加载供应商的dll,但这并没有释放泄漏的内存。我发现可以通过以下方式强制卸载该库:
_ctypes.FreeLibrary(vendor_dll._handle)

这会释放内存,但使用供应商的API一段时间后,解释器似乎会随机崩溃。
我在Python错误跟踪器中找到了描述我的情况的问题: https://bugs.python.org/issue14597 如果仍有对库的引用,强制卸载它将不可避免地导致Python解释器崩溃。
最坏的情况是,我考虑在单独的进程中加载供应商的库,使用多处理队列代理请求,并设置看门狗以在解释器死亡时重新创建进程。
是否有更好的方法来解决这个问题?

我认为更加优雅的解决方案是定期退出并重新启动工作进程。这将允许由供应商库泄漏的内存得到释放,但不会导致解释器崩溃。 - jakogut
如果释放库可以正常工作,那么它就不是真正的内存泄漏。该库必须跟踪这些分配以在DLL_PROCESS_DETACH上释放它们。至于卸载vendor_dll,您应该首先删除所有对当前实例的引用,并确保没有任何东西再次执行其代码,包括未完成的异步回调。然后您可以卸载和重新加载DLL。共享库是引用计数的,因此为了确保它实际上已被卸载,请调用FreeLibrary直到它引发一个OSError - Eryk Sun
1个回答

1
最终,我通过在单独的进程中加载供应商的库,并通过Pyro4访问它来解决了这个问题,方法如下:
class LibraryWorker(multiprocessing.Process):
    def __init__(self):
        super().__init__()

    def run(self):
        self.library = ctypes.windll.LoadLibrary(
            'vendor_library.dll')

        Pyro4.serveSimple(
            {self, 'library'},
            ns=False)

    def lib_func(self):
        res = self.library.func()
        return res

稍微修改一下旧代码以避免在两个进程之间传递ctypes指针是一些额外的工作,但它能够正常工作。

将库加载到单独的进程中,我可以跟踪内存使用情况。当内存使用量过高时,我可以终止并重新创建进程以释放内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接