rename()在同文件系统内重命名是原子操作,可静默替换目标文件且不可中断;跨文件系统需fallback到copy+unlink;os.rename()继承此特性,但需注意内容落盘需额外fsync。

rename() 系统调用本身就是原子的
在绝大多数 POSIX 兼容系统(Linux、macOS、FreeBSD 等)上,rename() 系统调用对同一文件系统内的文件重命名是原子操作。这意味着:如果目标路径 dst 已存在,它会被静默替换(前提是进程有写权限);整个操作不可被中断,也不会出现“只改了一半名字”的中间状态。
关键限制是:源和目标必须位于同一挂载点(即同个文件系统)。跨文件系统调用 rename() 会失败并返回 EXDEV 错误。
Python 中用 os.rename() 实现原子重命名
Python 的 os.rename() 直接封装了底层 rename() 系统调用,因此具备相同原子性保证(同样要求同文件系统)。
常见错误写法是先 os.remove(dst) 再 os.rename(src, dst) —— 这彻底破坏原子性,两步之间存在竞态窗口。
正确做法:
import os
try:
os.rename("temp_file.json", "config.json")
except OSError as e:
if e.errno == errno.EXDEV:
# 跨文件系统,需 fallback 到 copy + unlink
pass
else:
raise跨文件系统时无法原子重命名,必须手动处理
当 rename() 返回 EXDEV,说明源和目标不在同一文件系统。此时没有单系统调用能保证原子性,只能组合操作,并接受极小窗口期(但比手动删+写安全得多):
- 先
shutil.copy2(src, dst)(保留时间戳、权限) - 再
os.unlink(src)
注意:这个组合不是原子的。若在 copy 完成后、unlink 前崩溃,会残留临时文件;若在 copy 中断,则 dst 可能是不完整副本。生产环境如需强一致性,应配合校验、重试或使用临时目录+fsync。
Linux 上 renameat2() 提供更细粒度控制(较新内核)
Linux 3.15+ 支持 renameat2() 系统调用,可通过标志位增强语义,例如:
-
RENAME_EXCHANGE:原子交换两个路径的内容 -
RENAME_NOREPLACE:禁止覆盖已存在目标(避免意外删数据) -
RENAME_WHITEOUT:用于 overlayfs 场景
Python 标准库暂未暴露 renameat2(),需通过 ctypes 或第三方包(如 oslo.utils)调用。普通场景用 os.rename() 已足够。
真正容易被忽略的是:原子性只保证「路径映射变更」这一层,不保证文件内容已刷盘。若需落盘持久化,应在 rename() 前对源文件 os.fsync()(打开文件描述符后),否则重命名后仍可能丢失最近写入的数据。










