如何用ThreadPoolExecutor递归遍历目录?代码死循环排查与修复
问题分析与修复方案
嘿,我来帮你拆解下代码的问题:你的程序并不是真的无限运行,而是陷入了线程池死锁,导致卡住不动了。
核心问题原因
你用了ThreadPoolExecutor.map来提交递归任务,而map方法是阻塞式的——它会等待所有提交的任务执行完成后才会返回结果。当线程池的所有4个线程都被占用后,每个线程在处理目录时又会调用executor.map提交新的递归任务,但此时已经没有空闲线程去执行这些新任务了。于是所有线程都卡在等待自己提交的map任务完成,整个程序陷入死锁,看起来就像无限运行。
举个具体场景:主线程调用listdir('.', e),判断是目录后提交几个子目录的任务到线程池;线程池的4个线程开始处理这些子目录,每个线程在处理自己的子目录时,又提交新的任务,但这时候线程池已经没有空闲线程了,这些新任务只能排队;而每个线程都在等待自己提交的map任务完成,没人能去处理排队的任务,最终整个程序彻底卡住。
修复方案(基于Executor实现)
我们可以用executor.submit配合as_completed来替代map,这样可以避免阻塞等待所有任务,而是逐个处理完成的任务,让线程池的线程能循环利用,避免死锁。同时还能优化列表合并的效率:
from pathlib import Path from typing import List from concurrent.futures import ThreadPoolExecutor, Executor, as_completed def listdir(root: Path, executor: Executor) -> List[Path]: if root.is_dir(): # 用submit提交每个子目录的递归任务,获取Future对象列表 futures = [executor.submit(listdir, d, executor) for d in root.glob('*')] result = [] # 遍历已完成的任务,逐个收集结果 for future in as_completed(futures): result.extend(future.result()) return result return [root] with ThreadPoolExecutor(4) as e: all_files = listdir(Path('.'), e) # 可以打印结果验证 # for path in all_files: # print(path)
修复要点说明
- 用
submit替代map:submit不会阻塞等待任务完成,而是立即返回Future对象,让我们可以灵活控制任务的处理时机 - 用
as_completed处理结果:它会迭代返回已完成的Future,这样每个线程完成一个子目录的遍历后,就能释放出来处理新提交的任务,避免线程池耗尽 - 优化列表合并:原来的
sum(xss, [])每次合并都会创建新列表,时间复杂度是O(n²);改用extend直接在原列表添加元素,效率更高(O(n))
额外提示
如果你的最终目标是用Paramiko遍历远程目录,这个逻辑完全可以复用——只需要把Path相关的本地文件操作替换成Paramiko的SFTP客户端操作(比如sftp.listdir_attr、sftp.stat来判断是否是目录)即可。
内容的提问来源于stack exchange,提问作者Eastsun
相关产品推荐
相关产品推荐

