sync.mutex对文件并发写无效,因其仅锁进程内内存变量,无法阻止多进程、多pod或cron脚本同时写同一文件;跨进程必须用flock(linux/macos)或lockfileex(windows)等内核级文件锁。

sync.Mutex 不能解决并发写文件冲突——它只锁内存,不锁磁盘文件;多进程、多 Pod、cron 脚本同时跑时,sync.Mutex 形同虚设。
为什么框架自带的锁(如 sync.Mutex)对文件无效
Go 框架里常见的 sync.Mutex 或 sync.RWMutex 只作用于当前进程内 goroutine,完全不感知文件系统。只要启动第二个二进制实例(比如 K8s 新扩一个 Pod、手动再跑一次 ./app),锁就彻底失效。
- 两个进程都调用
os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0)→ 后者清空文件,前者接着写,内容全丢 - 读 goroutine 正在
file.Read(),另一个进程执行file.Truncate(0)→ 读到 EOF 或乱码 - 加了
mu.Lock()却仍出现覆盖写,说明你锁的是变量,不是文件本身
gofrs/flock 是最省心的跨平台文件锁方案
它自动处理 Linux/macOS 的 unix.Flock 和 Windows 的 LockFileEx,API 统一,不用条件编译,也不用自己判错。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 锁文件路径可任意,建议与业务文件分离,例如业务文件是
config.json,锁文件用config.json.lock - 初始化:
fileLock := flock.New("/path/to/config.json.lock"),锁文件无需预先存在 - 非阻塞获取:
locked, err := fileLock.TryLock(),失败时err == nil && !locked,不是 panic - 必须显式
fileLock.Unlock(),推荐放在defer里,避免 panic 导致锁残留 - 权限设为
0600,防止其他用户干扰
别把锁和写操作混在一起做耗时事
文件锁的临界区应严格限定在“打开→加锁→写入→关闭”这一小段,中间任何网络请求、sleep、JSON 解析、数据库查询,都得挪出去。
- 持有锁期间调用
http.Get()→ 其他进程卡住等几十秒,吞吐归零 - 用
json.Marshal在锁内生成字节 → 大对象序列化慢,放大争抢 - 正确做法:先序列化好数据,再开文件、加锁、
file.Write()、关文件 - 锁生命周期 = 文件描述符生命周期,
file.Close()会自动释放底层锁,但flock.Unlock()更明确、更可控
Linux 上直接用 unix.Flock 的关键限制
如果不想引入第三方依赖,Linux/macOS 下可用 golang.org/x/sys/unix,但有硬性约束:
- 文件必须以
os.O_RDWR打开,os.O_RDONLY下调unix.Flock(fd, unix.LOCK_EX)直接返回unix.EBADF - 不能带
os.O_CREATE | os.O_TRUNC—— 创建新 inode 会导致锁绑定失效(旧锁还在原 inode 上) - 加锁后不要重复调用
unix.Flock,第二次会覆盖前一次状态,不报错但逻辑错乱 - 非阻塞模式必须组合
unix.LOCK_NB,错误判断用err == unix.EWOULDBLOCK || err == unix.EAGAIN
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










