多线程处理大量.gz文件时卡在最后一个Future的问题排查
def download_files_from_folder(base_url, folder_name): folder_url = f"{base_url}{folder_name}/" response = requests.get(folder_url) soup = BeautifulSoup(response.content, "html.parser") links = soup.find_all("a") peninsular_rain_rates = [] east_rain_rates = [] completed_urls = [] with ThreadPoolExecutor(max_workers=15) as executor: futures = {executor.submit(process_gz_file, folder_url + link.get("href")): link.get("href") for link in links if link.get("href").endswith(".gz")} for future in as_completed(futures): peninsular, east, completed = future.result() peninsular_rain_rates.extend(peninsular) east_rain_rates.extend(east) completed_urls.extend(completed) futures.pop(future) print(len(futures))
这段代码用于从网站获取约8000个单文件大小约1.5MB的.gz文件链接并处理,将结果存入对应数组。使用ThreadPoolExecutor(最大工作线程数15),每完成一个Future就从字典中移除并打印剩余数量,但处理大量文件时卡在最后一个Future,处理少量文件(如50个)时运行正常,请问这是否是内存问题?
不一定是单纯的内存问题,可能是以下几种原因:
process_gz_file函数的潜在问题:最后一个文件的处理逻辑可能存在阻塞(比如未处理的异常、无限循环、网络重试卡住),或者该文件本身损坏/格式异常,导致线程无法返回结果。可以单独测试最后一个卡住的文件链接,验证调用process_gz_file是否能正常结束。内存占用过高引发的系统限制:8000个文件处理后,
peninsular_rain_rates、east_rain_rates、completed_urls三个数组会持续扩容,若每个文件处理后生成的数据量较大,累计内存占用可能触及系统阈值,导致操作系统限制进程资源,进而让最后一个线程的执行被卡住。可以监控进程的内存使用情况,或在业务允许的前提下,分批处理并临时清理部分数据。线程资源未正常释放:虽然用
with语句管理ThreadPoolExecutor,但如果process_gz_file中存在未关闭的资源(如未关闭的文件句柄、网络连接),可能导致线程无法正常退出,最后一个Future一直处于挂起状态。Future字典操作的隐性问题:
futures.pop(future)并非线程安全操作,不过这种情况更易引发报错而非卡住,概率相对较低。
排查建议:
- 打印最后一个待处理的文件名,单独调用
process_gz_file验证执行情况。 - 监控进程内存占用,确认是否在接近系统内存上限时出现卡住现象。
- 在
process_gz_file中添加详细日志,记录每个步骤的执行状态,定位是否有阻塞环节。 - 尝试降低线程池最大工作线程数(比如调到10),减少并发带来的资源竞争。
内容的提问来源于stack exchange,提问作者radastro

