go中sync.mutex仅限单进程,跨进程文件互斥须用系统级锁:linux/macos用unix.flock(fd, unix.lock_ex|unix.lock_nb)配合超时重试,windows需lockfileex或gofrs/flock库适配。

Go 里用 sync.Mutex 根本防不住多进程冲突——它只在单个进程内生效。真正要防止多个进程同时写同一文件,必须用操作系统级的文件锁,Linux/macOS 上首选 flock,Windows 上得换路子。
syscall.Flock 在 Linux/macOS 上怎么用才不翻车
标准库没封装 flock,得手动调系统调用。关键不是“能不能调”,而是“怎么调才安全”:
- 必须用
os.OpenFile打开文件,且带os.O_CREATE | os.O_RDWR(只读或只写都可能失败) -
syscall.Flock(int(f.Fd()), syscall.LOCK_EX)才是加独占锁;LOCK_SH是共享锁,仅限读场景,Windows 不支持 - 别手抖写成
syscall.LOCK_EX | syscall.LOCK_NB却不检查错误——失败时返回syscall.EWOULDBLOCK,不是nil - 锁绑定在 fd 上,不是路径;同一文件反复
OpenFile会得到不同 fd,各自独立加锁,互不影响 - 锁会在
f.Close()时自动释放,defer f.Close()是最稳妥的释放方式
为什么用 golang.org/x/sys/unix 而不是 syscall
syscall 包已弃用,跨平台兼容性差,比如 macOS 上某些常量名不一致。换成 golang.org/x/sys/unix 更稳:
- 导入后直接用
unix.Flock(int(f.Fd()), unix.LOCK_EX),语义清晰,常量定义统一 -
unix.LOCK_NB支持非阻塞尝试,配合time.AfterFunc或context.WithTimeout实现可控超时 - macOS、FreeBSD、Linux 都覆盖,未来新增 Unix 变种也大概率兼容
- Windows 下这个包不提供
flock实现,但至少不会编译报错,方便条件编译隔离
Windows 下没 flock 怎么办
Windows 原生不支持 flock 系统调用,syscall.Flock 直接未定义。硬上会编译失败:
- 要么用
golang.org/x/sys/windows调LockFileEx,但逻辑更复杂,需处理重试、句柄继承等细节 - 要么换第三方库如
github.com/gofrs/flock,它内部做了平台判断:Unix 走unix.Flock,Windows 走LockFileEx - 注意:Windows 的
LockFileEx不支持共享锁(LOCK_SH),所有锁都是独占语义 - 如果项目必须跨平台且只做写互斥,
github.com/gofrs/flock是目前最省心的选择,不用自己写// +build windows
锁住文件后还出问题?常见坑点
加了锁还丢数据、乱序、卡死,往往不是锁没起作用,而是用法踩了隐性雷:
-
defer f.Unlock()在 panic 或os.Exit()时根本不会执行,锁残留;正确做法是defer f.Close(),靠 close 自动释放 - goroutine 中加锁后做 HTTP 请求、数据库查询等耗时操作——锁持有时间过长,其他进程干等,甚至触发超时熔断
- 误以为锁住路径就锁住文件:
os.Open("a.log")和os.Open("b.log")指向同一个 inode(比如软链接),但锁是按 fd 绑定的,完全不互通 - 没验证所有写入方都加锁:flock 是建议性锁,一个进程绕过它直接写,其他加锁进程毫无感知
- 用
os.Create替代os.OpenFile,后者可能因权限或 flags 不匹配导致f.Fd()返回无效值,flock调用直接 panic
最易被忽略的一点:锁的粒度是整个文件,不是某段内容。想控制某一行或某个 JSON 字段的并发更新,flock 无能为力,得上 Redis 分布式锁或本地内存缓存 + 原子重命名方案。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











