Python多线程应用中线程特定日志实例的内存泄漏
我有一个服务器的子类,它会生成多个线程来处理响应,这些处理程序又会启动应用程序线程。整体运行得很顺利,除了当我使用ObjGraph时,我可以看到正确数量的应用程序线程在运行(我在进行负载测试,并且限制它保持35个应用实例在运行)。
调用objgraph.typestats()可以让我看到当前在解释器中活跃的每种对象的实例数量(根据垃圾回收机制)。查看这个输出以寻找内存泄漏时,我发现有700个logger实例——这正好是服务器生成的响应处理程序的总数。
我在应用程序线程退出run()方法时调用了logger.removehandler(memoryhandler)和logger.removehandler(filehandler),以确保没有残留的logger实例引用。此外,logger实例完全隔离在应用程序线程内(没有外部引用)。为了最后尝试消除这些logger实例,我在run()的最后一行写了del self.logger。
在init()中获取logger时,我给它一个合适的大随机数作为名称,以确保它在文件访问时是独特的——我在日志文件名中也使用了相同的大数字,以避免应用程序日志冲突。
总的来说,我有700个logger实例被垃圾回收机制跟踪,但只有35个活跃线程——我该如何处理这些logger?一个更麻烦的工程师解决方案是创建一个logger池,只为应用程序线程的生命周期获取一个logger,但这会增加更多的代码维护,而垃圾回收机制应该可以自动处理这些问题。
2 个回答
我在使用 logging.Logger() 的时候也遇到了同样的内存泄漏问题。你可以尝试在不需要这个日志记录器的时候,手动关闭它的处理器文件描述符,像这样:
for handler in logger.handlers:
handler.close()
不要创建可能会无限增加的日志记录器,这样做不好。其实还有其他方法可以把上下文相关的信息放到你的日志里,具体可以参考这里。
你也不需要把日志记录器作为实例属性。日志记录器是单例的,所以你可以随时通过名字获取特定的日志记录器。推荐的做法是在模块级别命名日志记录器,使用
logger = logging.getLogger(__name__)
这对于大多数情况来说已经足够了。
从你的问题中,我无法判断你是否明白处理器和日志记录器不是同一回事。比如你提到的removeHandler调用(这可能是为了释放处理器实例,因为它们的引用计数变为零,但这样做并不会释放任何日志记录器实例)。
一般来说,日志记录器的命名是根据你应用程序中生成重要事件的部分来命名的。
如果你想让每个线程写入不同的文件,你可以每次创建一个新的文件名,然后在完成后关闭处理器,等线程快要结束时再关闭(这个关闭操作很重要,可以释放处理器资源)。或者,你也可以把所有日志写入一个文件,并在日志输出中包含线程ID或其他区分信息,然后对日志文件进行后期处理。