Python多进程并发创建目录遇文件已存在错误,求解决方案
为什么1秒延迟没用?
你遇到的是典型的TOCTOU(检查时间与使用时间)竞态条件。os.path.exists(output_folder) 和 os.mkdir(output_folder) 这两个操作不是原子的——哪怕加了1秒sleep,也可能出现这种情况:第一个进程刚检查完目录不存在,还没来得及执行mkdir,操作系统就调度第二个进程运行,第二个进程同样检查到目录不存在,随后两个进程都尝试创建同一个目录,导致第二个进程抛出OSError: [Errno 17] File exists。
sleep只能降低竞态发生的概率,但无法彻底消除它,因为进程的执行时机完全由操作系统调度器控制,不是sleep能精准把控的。
最优解决方案
方案1:用异常捕获替代预检查(推荐,最简单)
直接尝试创建目录,捕获"文件已存在"的异常即可。因为os.mkdir在Unix-like系统上是原子操作——只要有一个进程成功创建了目录,其他进程的mkdir就会立刻抛出异常,不会出现两个进程都成功创建的情况。
修改你的create_output_directory函数:
import time def create_output_directory(): output_folder = 'output_%s' % datetime.now().strftime('%Y%m%d') try: os.mkdir(output_folder) logger.info('Created output directory %s' % output_folder) except OSError as e: if e.errno == 17: # 捕获"文件已存在"的异常 logger.warning('Previous directory %s exists, files may be overwritten' % output_folder) else: raise # 其他异常重新抛出,不忽略 # 处理软链接的竞态:循环重试直到成功 while True: try: if os.path.exists('output_latest'): os.remove('output_latest') os.symlink(output_folder, 'output_latest') break except OSError as e: logger.debug('Failed to update output_latest symlink, retrying...') time.sleep(0.1) return output_folder
这里对软链接的操作加了循环重试,因为多个进程同时删除/创建软链接也可能出现竞态(比如进程A刚删除output_latest,进程B也重复删除一次,随后进程A创建软链接,进程B再创建就会报错),循环重试可以解决这个问题。
方案2:使用文件锁(更严谨,适合复杂场景)
如果需要确保只有一个进程能执行创建目录和更新软链接的操作,可以用文件锁实现互斥。比如创建一个临时锁文件,只有拿到锁的进程才能执行目录创建逻辑:
import fcntl def create_output_directory(): output_folder = 'output_%s' % datetime.now().strftime('%Y%m%d') # 创建锁文件(不存在则自动创建) lock_file = open('.directory_lock', 'w') try: # 获取排他锁,阻塞直到拿到锁 fcntl.flock(lock_file, fcntl.LOCK_EX) # 现在只有当前进程能执行下面的逻辑 if not os.path.exists(output_folder): os.mkdir(output_folder) logger.info('Created output directory %s' % output_folder) else: logger.warning('Previous directory %s exists, files may be overwritten' % output_folder) if os.path.exists('output_latest'): os.remove('output_latest') os.symlink(output_folder, 'output_latest') finally: # 释放锁并关闭文件 fcntl.flock(lock_file, fcntl.LOCK_UN) lock_file.close() return output_folder
这种方式能彻底避免所有竞态,但需要注意:文件锁在Unix-like系统上有效,Windows系统需要用不同的锁机制(比如msvcrt.locking)。
总结
- sleep无法解决竞态条件,只能降低概率;
- 最简单的解决方案是用异常捕获+重试处理目录和软链接的创建;
- 如果需要严格的互斥,使用文件锁是更稳妥的选择。
内容的提问来源于stack exchange,提问作者Ruggero Turra

