原子写通过先写临时文件再用os.replace()原子替换解决崩溃丢数据问题,需flush+fsync落盘、同目录存放,并配合锁机制防并发覆盖。

为什么普通写入容易丢数据
程序崩溃时文件内容丢失,根本原因不是“没写完”,而是写入过程被中断后留下不完整或损坏的文件。比如用 open(..., 'w') 直接覆盖原文件,一旦在 write() 中途崩溃,原文件就永久清空了——连备份都没留下。
原子写的核心:先写新文件,再替换旧文件
原子写不直接操作目标文件,而是把新内容写入一个临时文件(如 data.json.tmp),写完并刷新磁盘缓存后,再用 os.replace() 原子地重命名它为正式文件名。这个重命名操作在绝大多数文件系统上是不可中断的,要么成功、要么失败,不会出现“半新半旧”状态。
关键点:
-
os.replace()是跨平台安全的(Python 3.3+),比os.rename()更可靠,能处理跨文件系统场景 - 必须调用
file.flush()和os.fsync(file.fileno())确保内容真正落盘,否则临时文件可能还在内存缓存里 - 临时文件应和目标文件放在同一目录下,否则
os.replace()可能退化为复制+删除,失去原子性
示例:
立即学习“Python免费学习笔记(深入)”;
import os
import tempfile
<p>def atomic_write(filepath, content):</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts"><img
src="https://img.php.cn/upload/skill/000/000/081/179231049778675.jpg" alt="361SCAN security check your skill python scripts" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts">361SCAN security check your skill python scripts</a>
<p>扫描并分析已安装技能,用于查看指定技能目录的名称、描述和详情,或列出所有已安装的技能。</p>
</div>
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><h1>创建同目录下的临时文件</h1><pre class="brush:php;toolbar:false;">dirpath = os.path.dirname(filepath) or '.'
tmp_fd, tmp_path = tempfile.mkstemp(dir=dirpath, suffix='.tmp')
try:
with os.fdopen(tmp_fd, 'w') as f:
f.write(content)
f.flush()
os.fsync(f.fileno())
# 原子替换
os.replace(tmp_path, filepath)
except Exception:
os.close(tmp_fd)
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise
多进程/多线程环境下仍需额外加锁
原子写本身解决的是“崩溃导致损坏”的问题,但不解决“并发写入冲突”。如果多个进程同时调用 atomic_write() 写同一个文件,它们各自生成的临时文件互不干扰,但最终的 os.replace() 谁先谁后无法控制——后执行的会覆盖先执行的结果。
所以真实场景中通常要组合使用:
- 用
threading.Lock或multiprocessing.Lock控制写入入口(适合单机多线程/多进程) - 用
fcntl.flock()对目标文件加排他锁(注意:锁的是文件描述符,不是路径;且 Windows 不支持) - 更稳妥的做法是让所有写请求都进一个专用写入线程/进程,其他模块只往
queue.Queue里塞数据
哪些情况原子写也救不了
原子写不是万能的。以下情形它无能为力:
- 磁盘满或权限不足导致
write()失败——这属于前置校验问题,得靠try/except捕获OSError - 写入前未校验数据格式(如 JSON 格式错误),导致新文件本身就不合法——应在写入前做数据验证
- 程序崩溃发生在
os.replace()之后、但用户还没读取到新内容时——这时数据其实已安全,只是感知延迟
最常被忽略的一点:临时文件路径的生成和清理必须严格配对。用 tempfile.mkstemp() 而非手动拼接字符串,避免竞态条件和路径遍历风险。

















