直接f.write()不安全,因数据可能滞留用户空间缓冲区或内核页缓存而未落盘;断电时易致文件截断、元数据损坏或文件系统不一致,须配合f.flush()和os.fsync(f.fileno())确保内容及元数据写入物理存储。

为什么直接 f.write() 不够安全
断电时如果只调用 f.write(),数据可能还卡在用户空间缓冲区或内核页缓存里,根本没落盘。Python 默认开启缓冲,write() 返回成功 ≠ 文件内容已写入磁盘。尤其在嵌入式设备或工控场景,这会导致文件截断、元数据损坏甚至整个文件系统不一致。
用 os.fsync() 强制刷盘
写完内容后必须显式调用 os.fsync(),它会把文件内容和元数据(如 mtime、size)一并刷到物理存储。注意不是 os.sync()(全局刷盘,开销大且没必要)。
关键步骤:
- 用
open(..., "w")打开文件,避免"a"模式(追加写可能绕过部分校验) -
write()后立即调用f.flush()—— 把 Python 缓冲区清空到内核 - 再调用
os.fsync(f.fileno())—— 把内核缓存强制刷入磁盘 - 最后
f.close()
示例:
立即学习“Python免费学习笔记(深入)”;
import os
with open("/path/to/data.txt", "w") as f:
f.write("hello world\n")
f.flush()
os.fsync(f.fileno())
更稳妥:先写临时文件再原子重命名
即使刷盘成功,如果写入中途崩溃,原文件仍可能被破坏。真正原子的方案是「写新文件 + 重命名」:重命名(os.rename())在同一个文件系统上是原子操作,要么全成功,要么失败不生效。
实操要点:
- 临时文件名要带随机后缀(如
data.txt.tmp.123abc),避免冲突 - 临时文件必须和目标文件在同一挂载点(否则
os.rename()会报EXDEV错误) - 写完临时文件后,务必先
fsync临时文件,再rename,最后对目标文件再fsync目录(确保目录项落盘)
简略流程:
import os
import tempfile
<h1>1. 创建同目录临时文件</h1><p>tmp_fd, tmp_path = tempfile.mkstemp(dir="/path/to", suffix=".tmp")
with os.fdopen(tmp_fd, "w") as f:
f.write("new content\n")
f.flush()
os.fsync(f.fileno())</p><h1>2. 原子替换</h1><p>target = "/path/to/data.txt"
os.replace(tmp_path, target) # Python 3.3+,等价于 os.rename</p><h1>3. 确保目录更新落盘(关键!)</h1><p>os.fsync(os.open(os.path.dirname(target), os.O_RDONLY))
哪些情况会失效?
上述方法依赖底层文件系统行为,容易被忽略的边界:
- 某些 SSD 或 USB 设备宣称支持
fsync,实际固件会谎报完成(称为“fake fsync”),此时需硬件级掉电保护或禁用写缓存(hdparm -W0 /dev/sdX) -
ext4默认启用data=ordered,但若挂载时加了barrier=0或journal=writeback,fsync语义会被削弱 - 容器环境里,宿主机挂载选项、overlayfs 层叠可能干扰原子性,得在最外层宿主机确认挂载参数
真要防断电,不能只靠代码层 —— 刷盘是否生效,最终得看硬件响应和文件系统配置是否可信。


















