背景:
使用python进行大量的数据操作过程中RSS占用(几个G,Python的GC会频繁地malloc/free),发现RSS内存不释放。
- 排查代码后,没有发现内存泄露的情况,GC也是默认开启的,甚至代码将数据库数据读出来遍历一遍后程序结束,内存一直存在不释放。
- 使用tracemalloc,objectgraph调试,Python对象的产生和释放并没有啥异常。
- Python的对象内存管理是基于引用计数的(refcnt为0直接decref回收),python内存池也没发现大内存驻留,操作系统brk和mmap也并没有不释放内存。
在调测中也发现,这个问题给人的整体感觉不是代码哪里有问题,查阅相关资料后,发现可能是Glibc优化问题。
python的内存机制,频繁通过系统调用获取和释放内存对性能消耗很大的,python将对象销毁后,并没有立即将这部分内存返回给操作系统,而是加到了自己维护的空闲内存池中。从系统层面来看,这步扥内存已经被python进程占用了,但是从python解释器的角度来看,这部分是free的,可以用于生成新的对象。
那么我们在Python中如何手动分配释放内存?
我们使用进程内存隔离的能力直接管理内存,父进程只负责进程池管理,子进程干完事情,直接退出,或过期重新拉起,或者超过内存阈值就干掉,就可以了(当然最好是业务处理不是很频繁的情况下)。
多进程处理参考:multiprocessing --- 基于进程的并行 — Python 3.10.0 文档
Process
类
在 multiprocessing 中,通过创建一个 Process 对象然后调用它的 start()
方法来生成进程。 Process 和 threading.Thread API 相同。 一个简单的多进程程序示例是:
from multiprocessing import Process
def f(name):
print('hello', name)
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
常见问题:
实际业务中会经常用到数据库等长连接的操作,如数据库等,可能会报如下错误:
ERROR:base.py:Line 705:_finalize_fairy:Exception during reset or similar
Traceback (most recent call last):
File "/home/tsalazar/.cache/pypoetry/virtualenvs/ufos--PZ7y9g--py3.8/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 697, in _finalize_fairy
fairy._reset(pool)
File "/home/tsalazar/.cache/pypoetry/virtualenvs/ufos--PZ7y9g--py3.8/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 893, in _reset
pool._dialect.do_rollback(self)
File "/home/tsalazar/.cache/pypoetry/virtualenvs/ufos--PZ7y9g--py3.8/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 558, in do_rollback
dbapi_connection.rollback()
psycopg2.OperationalError: SSL error: decryption failed or bad record mac
解决办法:不要将长连接对象通过参数传递,在函数内部链接,使得进程之间互不影响。
参考:
- 《python源码剖析》Cpython代码
- MySQL 内存管理初探
- glibc malloc常驻内存不释放问题抽象