tracemalloc解决Python内存泄露问题

点击上方“Python学习开发”,选择“加为星标”

第一时间关注Python技术干货!

原文:https://www.cnblogs.com/zhaof/p/10031945.html

最近工作中慢慢开始用python协程相关的东西,所以用到了一些相关模块,如aiohttp, aiomysql, aioredis等,用的过程中也碰到的很多问题,这里整理了一次内存泄漏的问题

通常我们写python程序的时候也很少关注内存这个问题(当然可能我的能力还有待提升),可能写c和c++的朋友会更多的考虑这个问题,但是一旦我们的python程序出现了

内存泄漏的问题,也将是一件非常麻烦的事情了,而最近的一次代码中也碰到了这个问题,不过好在最后内存溢出不是我代码的问题,而是所用到的一个包出现了内存的问题,下面我通过一个简单的代码模拟出内存的问题,然后也会将解决的过程描述一下,希望能帮助到遇到同样问题的朋友。

一、复现问题

其实这次主要是在使用aiohttp写一个接口的时候出现的问题,其实复现出问题非常容易,我们实现一个简单的接受post请求接口的服务端,然后实现一个并发的客户端来访问这个接口,来查看内存的情况

注意:这个问题是在一个包的特定版本出现的:multidict==4.5.1,我在整理这个文章2个小时前作者已经修复了这个问题发布了4.5.2版本,已经修复了内存的问题,并且我也进行了测试验证

服务端代码:

from aiohttp import web

async def hello(request):
    return web.json_response(await request.json())

app = web.Application()
app.add_routes([web.post('/', hello)])
web.run_app(app)

客户端代码:

import asyncio
import aiohttp

async def foo(times):
    data = {'foo': 1}
    async with aiohttp.ClientSession() as session:
        for x in range(times):
            resp = await session.post('http://localhost:8080', json=data)
            if not x % 100:
                print(await resp.json())

loop = asyncio.get_event_loop()
loop.run_until_complete(foo(100000))
loop.close()

因为我的代码是在linux上跑的,或者mac上我们都可以通过htop非常方面的实时查看我们程序内存的占用情况,我们先将服务端启动,查看一下我们此时的内存情况可以看到占用的

非常少,当我们打开客户端之后,再次观察我们可以看到内存不断增长,及时我们客户端运行完毕内存也不会降低。

img

当客户端结束之后的内存:

img

如果客户端不停止的话内存会一直涨,最后的结果就是把你的系统内存吃完,然后被系统杀掉你的进程。

二、解决内存泄漏的过程

像上面的例子是一个非常简单的程序,不复杂我们也并没有做上面复杂的操作就是一个简单的接受post请求的服务端,但是如果是在实际的项目中我们可能会写非常复杂的业务逻辑,那到时候我们又如何找到是哪里导致的内存问题,当我碰到这个问题的时候,其实我和很多接触python不久的人差不多,也是不知道怎么查这种问题,各种百度各种查,也找到了好多推荐的工具,memory_profiler库,objgraph库,graphviz工具,但是都没有帮助我迅速的找到问题点在哪里,最后看到标准库中的tracemalloc,地址:https://docs.python.org/3/library/tracemalloc.html

通过这个包很快帮我找到了内存泄漏的地方

接下来按照官网的方法我将代码进行改写,来测试到底哪里的问题导致的内存泄漏,更改后的服务端代码为:

from aiohttp import web
import tracemalloc


async def hello(request):
    return web.json_response(await request.json())

async def get_info(request):
    snapshot2 = tracemalloc.take_snapshot()
    top_stats = snapshot2.compare_to(snapshot1, 'lineno')
    print(top_stats)
    return web.Response(text="ok")


if __name__ == '__main__':
    app = web.Application()
    app.add_routes(
        [
            web.post('/', hello),
            web.get("/get_info", get_info)
        ]
    )
    tracemalloc.start()
    snapshot1 = tracemalloc.take_snapshot()
    web.run_app(app)
注意print(top_stats)这行打印的结果最后要关注

其实这里就是新增加了一个路由get_info, 我们启动服务端之后开启客户端,当我们客户端运行完毕之后,可以看到内存已经涨上去了,并且没有不会释放,这个时候,可以直接通过浏览器访问get_info这个路由看看print打印的内容,这里将会打印出你程序运行到这个时候那一行的代码内存增长的比较多,进行一次排序,前面的几个其实都是需要你关注的,因为这里数据较多,我就只打印如下前几个数据

<statisticdiff traceback="<Traceback" (

<statisticdiff traceback="<Traceback" (

<statisticdiff traceback="<Traceback" (

<statisticdiff traceback="<Traceback" (

<statisticdiff traceback="<Traceback" (

我们拿第一行来说,我们可以非常清楚的指导web_response的56行代码导致内存增长的最多,当然如果是我们复杂的项目也可以通过类似的方法,这样就可以非常快捷的找到我们代码中哪些地方会造成内存溢出,便于排查问题,我们点进去看看这行代码:

img

我们找到最终行,这个时候我们大致就可以看出哪里的问题了,我们接着看 CIMultiDict

class CIMultiDict(MultiDict):

    def _title(self, key):
        return key.title()

我们可以看到这个它继承 MultiDict 其实这里我们已经应该知道问题就是处在这个MultiDict上了

而这个最终其实最终就是MultiDict这个包,问题出在了这个包上,这个项目是在这里维护的:https://github.com/aio-libs/multidict

查看这个包的时候看到了,果然有人和我遇到了同样的问题,问题就是出在这里了,已经有人提交了bug

https://github.com/aio-libs/multidict/issues/307

不过不得不说国外的程序员真的是热爱自己的职业,很快这个问题得到了aio-libs小组中人的回应,问题也在我整理这个博客的时候被修复了,在最新的版本:4.5.2中已经测试没有内存泄漏的问题

三、总结

在这里处理的过程中,其实发现了自己很多的不足,查找问题的方式,以及遇到这种问题的解决思路,不过经过这次,至少下次遇到同样的问题,自己能很快的去查找

以及解决问题,还有就是针对https://docs.python.org/3/library/tracemalloc.html这个库的使用,也推荐大家多了解一下。

推荐阅读

Python 爬虫面试题 170 道:2019 版

pdb调试神器使用终极指南

一文教你读懂 Python 中的异常信息

添加微信[gopython3].回复:回复Go或者Python加对应技术群。

Python内存泄露是指在程序中存在未释放的内存空间问题,导致内存持续增长,最终可能导致程序崩溃或性能下降。下面是一些常见的查找和解决Python内存泄露问题的方法: 1. 使用内存分析工具:可以使用第三方库如`heapy`或`pympler`等进行内存分析。这些工具可以帮助你查看程序中的对象、内存使用情况和引用关系,从而找到可能导致内存泄露的原因。 2. 检查引用计数:Python中的内存管理是基于引用计数的,当一个对象没有引用时,会被垃圾回收机制自动回收内存。因此,检查对象的引用计数是否正常是查找内存泄露的一种方法。 3. 检查循环引用:在Python中,如果两个对象互相引用,而没有其他对象引用它们,那么这种情况下的内存就无法被垃圾回收机制回收,出现内存泄露。因此,需要检查代码中的循环引用问题,及时解除这些引用。 4. 检查资源释放:有些资源在使用后需要手动释放,比如打开的文件、数据库连接等。如果在使用完毕后没有及时关闭或释放,就会造成资源泄露,进而导致内存泄露。因此,务必确保在使用完毕后进行适当的关闭或释放操作。 5. 减少不必要的缓存:缓存常常是提高程序性能的手段,但是过多的缓存可能会导致内存占用增加。因此,需要评估和控制缓存数据的大小,避免不必要的数据缓存。 总之,查找和解决Python内存泄露问题需要使用工具进行内存分析,检查引用计数和循环引用,确认是否释放资源,以及减少不必要的缓存。通过这些方法,可以有效地定位内存泄露问题,并采取相应的措施加以解决
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值