點擊上方「Python學習開發」,選擇「加爲星標」html
第一時間關注Python技術乾貨!python
原文:https://www.cnblogs.com/zhaof/p/10031945.htmlmysql
最近工做中慢慢開始用python協程相關的東西,因此用到了一些相關模塊,如aiohttp, aiomysql, aioredis等,用的過程當中也碰到的不少問題,這裏整理了一次內存泄漏的問題linux
一般咱們寫python程序的時候也不多關注內存這個問題(固然可能個人能力還有待提高),可能寫c和c++的朋友會更多的考慮這個問題,可是一旦咱們的python程序出現了c++
內存泄漏的問題,也將是一件很是麻煩的事情了,而最近的一次代碼中也碰到了這個問題,不過好在最後內存溢出不是我代碼的問題,而是所用到的一個包出現了內存的問題,下面我經過一個簡單的代碼模擬出內存的問題,而後也會將解決的過程描述一下,但願能幫助到遇到一樣問題的朋友。git
1、復現問題
其實此次主要是在使用aiohttp寫一個接口的時候出現的問題,其實復現出問題很是容易,咱們實現一個簡單的接受post請求接口的服務端,而後實現一個併發的客戶端來訪問這個接口,來查看內存的狀況程序員
注意:這個問題是在一個包的特定版本出現的:multidict==4.5.1,我在整理這個文章2個小時前做者已經修復了這個問題發佈了4.5.2版本,已經修復了內存的問題,而且我也進行了測試驗證github
服務端代碼:web
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很是方面的實時查看咱們程序內存的佔用狀況,咱們先將服務端啓動,查看一下咱們此時的內存狀況能夠看到佔用的
很是少,當咱們打開客戶端以後,再次觀察咱們能夠看到內存不斷增加,及時咱們客戶端運行完畢內存也不會下降。
當客戶端結束以後的內存:
若是客戶端不中止的話內存會一直漲,最後的結果就是把你的系統內存吃完,而後被系統殺掉你的進程。
2、解決內存泄漏的過程
像上面的例子是一個很是簡單的程序,不復雜咱們也並無作上面複雜的操做就是一個簡單的接受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行代碼致使內存增加的最多,固然若是是咱們複雜的項目也能夠經過相似的方法,這樣就能夠很是快捷的找到咱們代碼中哪些地方會形成內存溢出,便於排查問題,咱們點進去看看這行代碼:
咱們找到最終行,這個時候咱們大體就能夠看出哪裏的問題了,咱們接着看 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中已經測試沒有內存泄漏的問題
3、總結
在這裏處理的過程當中,其實發現了本身不少的不足,查找問題的方式,以及遇到這種問題的解決思路,不過通過此次,至少下次遇到一樣的問題,本身能很快的去查找
以及解決問題,還有就是針對https://docs.python.org/3/library/tracemalloc.html這個庫的使用,也推薦你們多瞭解一下。
推薦閱讀
添加微信[gopython3].回覆:回覆Go或者Python加對應技術羣。
本文分享自微信公衆號 - Python學習開發(python3-5)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。