请教 Python 多线程内存不释放怎么排查

2020-10-06 23:51:58 +08:00
 r150r

新手写了一个多线程的爬虫,所有线程都执行完了,但是一直占着 1.5GB 的内存(任务数越多不释放的内存越多) 不知道怎么排查哪里出问题,pympler 看不太懂问题到底出在哪里,请教该如何正确的排查问题

执行多线程函数的代码:

    def mainfunc(tasknum, thread):
        tr = tracker.SummaryTracker()
        tr.print_diff()
        list = []
        for i in range(tasknum):
            list.append(str(i))
        pool = threadpool.ThreadPool(thread)
        requests = threadpool.makeRequests(childfunc, list)
        for req in requests:
            pool.putRequest(req)
        pool.wait()
        tr.print_diff()

tr.print_diff()打印的内容

初始化:

                     types |   # objects |   total size
========================== | =========== | ============
                      list |        3741 |    350.84 KB
                       str |        3739 |    260.01 KB
                       int |         673 |     18.40 KB
                      dict |           2 |    352     B
                     tuple |           4 |    256     B
                      code |           1 |    144     B
     function (store_info) |           1 |    136     B
                      cell |           2 |     96     B
  functools._lru_list_elem |           1 |     80     B
                    method |          -1 |    -64     B

所有线程结束后:

                                types |   # objects |   total size
===================================== | =========== | ============
                                 dict |      202860 |     43.69 MB
                                 list |      100169 |      8.47 MB
                                  str |      102446 |      5.62 MB
               threadpool.WorkRequest |      100000 |      5.34 MB
                                  int |      100836 |      3.08 MB
                   _io.BufferedReader |         294 |      2.35 MB
                                tuple |        1480 |     93.30 KB
                                 type |          76 |     85.98 KB
                                 code |         572 |     80.57 KB
                                bytes |        1219 |     51.49 KB
                                  set |          32 |     43.50 KB
                        socket.socket |         294 |     27.56 KB
       pymysql.connections.Connection |         294 |     16.08 KB
                      socket.SocketIO |         294 |     16.08 KB
  DBUtils.SteadyDB.SteadyDBConnection |         294 |     16.08 KB
6621 次点击
所在节点    Python
25 条回复
scriptB0y
2020-10-06 23:59:43 +08:00
线程池里面的任务,检查一下所有的函数最后都有 return,没有的加一下,再试试。
r150r
2020-10-07 00:15:05 +08:00
@scriptB0y 所有的函数都有 return 了
wevsty
2020-10-07 00:51:33 +08:00
检查有没有循环引用数据结构的问题。
Hstar
2020-10-07 00:56:58 +08:00
把你代码复制跑了一遍,是你代码里 requests 这个变量的问题,这个变量缓存了你所有任务的引用,只要你的 mainfunc 函数不结束这些引用就不会消失。
你可以在 mainfunc 外面套一层函数再打印 tr.print_diff()看看,会发现内存占用消失了。
也可以把
for req in requests:
pool.putRequest(req)
改成
while requests:
pool.putRequest(requests.pop())
r150r
2020-10-07 01:42:38 +08:00
@Hstar 谢谢解答!改成 requests.pop()后打印 tr.print_diff(),list 和 dict 明显少了。不过 htop 显示 1.5G 内存还是没释放,除非这个 mainfunc 结束,看来是 childfunc 的问题。
byaiu
2020-10-07 05:15:41 +08:00
内存分配是有状态的
superrichman
2020-10-07 06:41:09 +08:00
爬虫,你是不是用了 beautiful soup ?这个用完了要手动 decompose 一下,不然内存会爆炸
zhuangzhuang1988
2020-10-07 09:07:33 +08:00
不好查
国内的 python 核心开发着也扯到了 https://pythonhunter.org/episodes/9
noobsheldon
2020-10-07 09:11:06 +08:00
把这个 mainfunc 放入一个子进程执行, 子进程结束,让系统自己回收内存呢?
mumbler
2020-10-07 09:24:17 +08:00
“del 变量名” 可以手动释放内存
cloudyplain
2020-10-07 10:35:57 +08:00
1.threadpool 改为全局? 2.换 tcmalloc
cheng6563
2020-10-07 12:14:15 +08:00
不会 python
现代的 gc 一般就算回收了内存也不会把内存还给操作系统
可以考虑新启一个进程,操作完结束进程
chenqh
2020-10-07 13:06:59 +08:00
req 是什么东西?
r150r
2020-10-07 13:41:31 +08:00
@superrichman 没有使用 beautiful soup
wangritian
2020-10-07 13:46:30 +08:00
变量释放后,可能仅仅被 py 标记为垃圾,并没有归还操作系统,下次你再申请变量优先从垃圾堆里找
开子进程用完销毁是最可行的方案,另外找找有没有像 go 的 debug.FreeOSMemory()这种强制归还操作系统的函数
r150r
2020-10-07 13:51:02 +08:00
@noobsheldon 目前是把 mainfunc 放入子进程,分段每 100000 个任务执行 1 次
可目标站的 tid 越高,需要解析的资源就越多,每 100000 个任务需要的内存也就越来越高。
现在 100000 个任务要 26GB 内存了,只能手动调整任务数
r150r
2020-10-07 13:53:19 +08:00
已更新可以复现问题的最小化代码
chenqh
2020-10-07 13:54:02 +08:00
不用 threadpool 试试?
r150r
2020-10-07 14:02:07 +08:00
@wangritian 只保持 50 个线程 get 一个相同链接,内存使用量却跟随任务数量无止尽增长,请问这是没有被标记为垃圾,所以无法回收吗?
r150r
2020-10-07 14:09:42 +08:00
@chenqh 之前试过 multiprocessing.dummy 也不释放

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/712846

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX