os.writefile不是原子操作,因其内部使用os.o_trunc标志一打开文件即清空内容,若写入中途崩溃,原文件将丢失且无法恢复;真正原子写入需用临时文件+os.rename方案。

直接用 os.WriteFile 就行,但要注意它本质是“截断+重写”,不是原子操作
为什么 os.WriteFile 会清空原文件
os.WriteFile 内部调用的是 os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)。关键在 os.O_TRUNC —— 一打开文件就清空内容,不管后续写入是否成功。如果程序 panic、磁盘满或被 kill -9,原文件已空,新数据又没写完,就彻底丢数据。
- 这不是“写一半”,是“先毁后写”,中间态不可逆
- 即使你接着调
os.Chmod或os.Chown,也改不回已被截断的内容 - 在 NFS 或某些容器挂载点上,
os.O_TRUNC的行为可能更不稳定
想安全清空再写入,得自己控制打开模式
如果你需要“清空后立即写入”且能精确控制过程(比如写入前校验、写入中失败要保留原文件),就不能依赖 os.WriteFile 的封装,而应手动用 os.OpenFile:
- 用
os.O_CREATE | os.O_WRONLY | os.O_TRUNC打开,确保清空 - 写入完成后显式调
file.Sync(),强制刷盘(尤其重要于日志、配置等关键文件) - 务必检查
file.Close()的返回值——它可能携带 write sync 阶段的错误
示例:
file, err := os.OpenFile("config.json", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer func() {
if cerr := file.Close(); cerr != nil && err == nil {
err = cerr
}
}()
if _, err = file.Write(data); err != nil {
return err
}
if err = file.Sync(); err != nil {
return err
}
真正需要原子性?必须用临时文件 + os.Rename
当“要么全成功、要么原文件完好”是硬要求(如数据库 WAL、配置热更新),os.WriteFile 和手动 O_TRUNC 都不满足。唯一可靠路径是:
- 在同文件系统下生成临时文件(
os.CreateTemp最稳妥) - 完整写入并
Sync() - 用
os.Rename替换原文件(Linux/macOS 同设备下原子;Windows 要求目标路径无句柄占用) - 失败时主动清理临时文件(
os.Remove),避免残留
注意:os.Rename 跨挂载点会报 invalid cross-device link,必须提前 os.Stat 检查源和目标是否同 dev。
最易被忽略的一点:很多人以为“清空再写”只是个简单覆盖动作,其实它的安全边界完全取决于你是否接受“截断即生效”这个不可逆前提。生产环境里,只要文件有状态含义(配置、计数器、事务日志),就该默认走临时文件路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











