新手写了一个多线程的爬虫,所有线程都执行完了,但是一直占着 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
附上可以复现问题的最小化代码,执行完输出done后,htop显示python3一直占用着那一部分内存,除非kill掉否则不释放(发不了链接base64编码了一下)
#!/usr/bin/pyyhon
# -*- coding: UTF-8 -*-
import threadpool, time, requests, base64
s = requests.Session()
def childfunc(id):
url = base64.b64decode('aHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy9mL2ZmL1BpemlnYW5pXzEzNjdfQ2hhcnRfMTBNQi5qcGc=')
res = s.get(url, timeout=(5, 60))
def mainfunc(tasknum, thread):
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()
print('done')
while True:
time.sleep(1)
if __name__ == '__main__':
mainfunc(10000, 50)
如果把代码里的session.requests替换成str = ' ' * (500 * 1024 * 1024),使用的内存会马上就归还给系统
#!/usr/bin/pyyhon
# -*- coding: UTF-8 -*-
import threadpool, time
def childfunc(id):
#这htop显示占用500m
str = ' ' * (500 * 1024 * 1024)
time.sleep(10)
def mainfunc(tasknum, thread):
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()
print('done')
#这htop显示已释放500m
while True:
time.sleep(1)
if __name__ == '__main__':
mainfunc(1, 1)
1
scriptB0y 2020-10-06 23:59:43 +08:00
线程池里面的任务,检查一下所有的函数最后都有 return,没有的加一下,再试试。
|
3
wevsty 2020-10-07 00:51:33 +08:00
检查有没有循环引用数据结构的问题。
|
4
Hstar 2020-10-07 00:56:58 +08:00 3
把你代码复制跑了一遍,是你代码里 requests 这个变量的问题,这个变量缓存了你所有任务的引用,只要你的 mainfunc 函数不结束这些引用就不会消失。
你可以在 mainfunc 外面套一层函数再打印 tr.print_diff()看看,会发现内存占用消失了。 也可以把 for req in requests: pool.putRequest(req) 改成 while requests: pool.putRequest(requests.pop()) |
5
r150r OP @Hstar 谢谢解答!改成 requests.pop()后打印 tr.print_diff(),list 和 dict 明显少了。不过 htop 显示 1.5G 内存还是没释放,除非这个 mainfunc 结束,看来是 childfunc 的问题。
|
6
byaiu 2020-10-07 05:15:41 +08:00 via Android
内存分配是有状态的
|
7
superrichman 2020-10-07 06:41:09 +08:00 via iPhone
爬虫,你是不是用了 beautiful soup ?这个用完了要手动 decompose 一下,不然内存会爆炸
|
8
zhuangzhuang1988 2020-10-07 09:07:33 +08:00
不好查
国内的 python 核心开发着也扯到了 https://pythonhunter.org/episodes/9 |
9
noobsheldon 2020-10-07 09:11:06 +08:00
把这个 mainfunc 放入一个子进程执行, 子进程结束,让系统自己回收内存呢?
|
10
mumbler 2020-10-07 09:24:17 +08:00 via Android
“del 变量名” 可以手动释放内存
|
11
cloudyplain 2020-10-07 10:35:57 +08:00 via iPhone
1.threadpool 改为全局? 2.换 tcmalloc
|
12
cheng6563 2020-10-07 12:14:15 +08:00
不会 python
现代的 gc 一般就算回收了内存也不会把内存还给操作系统 可以考虑新启一个进程,操作完结束进程 |
13
chenqh 2020-10-07 13:06:59 +08:00 via Android
req 是什么东西?
|
14
r150r OP @superrichman 没有使用 beautiful soup
|
15
wangritian 2020-10-07 13:46:30 +08:00
变量释放后,可能仅仅被 py 标记为垃圾,并没有归还操作系统,下次你再申请变量优先从垃圾堆里找
开子进程用完销毁是最可行的方案,另外找找有没有像 go 的 debug.FreeOSMemory()这种强制归还操作系统的函数 |
16
r150r OP @noobsheldon 目前是把 mainfunc 放入子进程,分段每 100000 个任务执行 1 次
可目标站的 tid 越高,需要解析的资源就越多,每 100000 个任务需要的内存也就越来越高。 现在 100000 个任务要 26GB 内存了,只能手动调整任务数 |
17
r150r OP 已更新可以复现问题的最小化代码
|
18
chenqh 2020-10-07 13:54:02 +08:00
不用 threadpool 试试?
|
19
r150r OP @wangritian 只保持 50 个线程 get 一个相同链接,内存使用量却跟随任务数量无止尽增长,请问这是没有被标记为垃圾,所以无法回收吗?
|
21
r150r OP python 标记变量为垃圾而不释放有什么条件吗?
如果把代码里的 session.get 替换成 str = ' ' * (500 * 1024 * 1024),使用的内存会马上就归还给系统,是 requests 的问题吗 |
22
mywaiting 2020-10-07 17:06:07 +08:00
之前遇到过类似的问题,把 requests 的 timeout 调小一点吧 timeout=(5, 10) 试试
|
23
changePro 2020-10-08 22:18:26 +08:00
这个问题我今晚研究了下,Py 自己管理内存
``` str = ' ' * (500 * 1024 * 1024) ``` 这段代码有可能是在栈上面的,用完了 frame 就没了,内存自然释放 但是 ``` res = s.get(url, timeout=(5, 60)) ``` 有可能是在堆上面的,GC 回收的话,应该有内在策略,找时间可以分析分析内存布局 |
24
HappyTrail 2020-10-10 14:01:23 +08:00
https 改成 http 试试看 - -
|
25
nisonGe 2020-10-12 00:08:39 +08:00
个人猜测是因为有大量的异常导致,异常递归。task 越多,异常越多,内存占用也越多。
|