在Python程序运行过程中,打开文件、网络连接、管道等资源时都会分配对应的文件描述符,如果忘记手动关闭或者异常场景下没有正确释放,就会出现未关闭的文件描述符,长期运行会导致资源耗尽。我们可以通过多种方式检测这类问题。

基础检测:使用标准库psutil
psutil是跨平台的系统监控库,可以获取进程当前的文件描述符使用情况,通过对比不同阶段的文件描述符数量,就能判断是否存在未关闭的情况。
首先需要安装psutil库:
pip install psutil
下面是一个简单的检测示例,对比打开文件前后的描述符数量变化:
import psutil
import os
# 获取当前进程对象
current_process = psutil.Process(os.getpid())
# 统计打开前的文件描述符数量
before_open = len(current_process.open_files())
print(f"打开前文件描述符数量:{before_open}")
# 模拟打开文件但不关闭
f = open("test.txt", "w")
# 这里没有调用f.close()
# 统计打开后的文件描述符数量
after_open = len(current_process.open_files())
print(f"打开后文件描述符数量:{after_open}")
# 输出未关闭的文件信息
if after_open > before_open:
print("存在未关闭的文件描述符:")
for file_info in current_process.open_files():
print(f"文件路径:{file_info.path},文件描述符:{file_info.fd}")
上下文管理器场景的检测
使用with语句可以自动关闭文件描述符,但如果自定义的资源类没有实现上下文管理器协议,还是可能出现泄漏。我们可以通过重写__del__方法或者结合弱引用检测对象是否被回收,间接判断描述符是否关闭。
下面是一个自定义资源类的检测示例:
import weakref
class CustomResource:
def __init__(self, name):
self.name = name
self.fd = open(f"{name}.txt", "w") # 模拟分配文件描述符
print(f"资源{name}初始化,分配描述符")
def close(self):
if not self.fd.closed:
self.fd.close()
print(f"资源{name}的描述符已关闭")
def __del__(self):
# 对象被销毁时如果描述符未关闭,打印警告
if hasattr(self, "fd") and not self.fd.closed:
print(f"警告:资源{self.name}被销毁时描述符未关闭")
# 检测对象回收情况
def resource_finalizer(ref):
print("资源对象已被垃圾回收")
res = CustomResource("demo")
# 创建弱引用,对象被回收时触发回调
ref = weakref.ref(res, resource_finalizer)
# 不手动调用close,也不删除引用,触发__del__
del res
长期运行服务的监控方案
对于长期运行的服务,我们可以定期采样进程的文件描述符数量,设置阈值告警。下面是一个简单的定时监控示例:
import psutil
import os
import time
def monitor_fd(interval=5, threshold=100):
"""
定期监控文件描述符数量
:param interval: 监控间隔,单位秒
:param threshold: 告警阈值
"""
process = psutil.Process(os.getpid())
while True:
fd_count = len(process.open_files())
print(f"当前文件描述符数量:{fd_count}")
if fd_count > threshold:
print(f"告警:文件描述符数量超过阈值{threshold}")
# 这里可以添加告警逻辑,比如发送通知
time.sleep(interval)
if __name__ == "__main__":
# 启动监控,间隔3秒,阈值设为50
monitor_fd(interval=3, threshold=50)
常见泄漏原因与修复建议
未关闭的文件描述符通常由以下原因导致:
- 打开资源后忘记调用close方法,也没有使用with语句
- 异常场景下,close方法没有被执行,比如没有在finally块中释放资源
- 自定义资源类没有实现上下文管理器协议,使用with语句也无法自动释放
对应的修复建议:
- 优先使用with语句管理资源,例如
with open("test.txt", "r") as f:,离开with块会自动关闭 - 如果必须手动管理,将close方法放在finally块中执行,确保异常时也能释放
- 自定义资源类实现__enter__和__exit__方法,支持上下文管理器协议
补充:使用lsof命令辅助检测
除了Python代码内检测,我们也可以使用系统命令lsof查看指定进程的文件描述符情况,适合定位线上问题。例如查看进程ID为1234的进程打开的所有文件:
lsof -p 1234
如果需要过滤Python进程的文件描述符,可以结合grep使用:
lsof -p 1234 | grep python