syscall.Flock 是跨进程文件锁的首选方案,轻量可靠、内核维护、无需外部依赖,但需所有进程主动加锁且仅支持 Unix-like 系统。

syscall.Flock 是跨进程文件锁的首选方案
在多实例部署(比如多个微服务 Pod、定时任务并发执行)场景下,syscall.Flock 是最轻量、最可靠的选择。它不依赖外部组件,直接由内核维护锁状态,所有进程只要对同一文件路径调用 flock,就能达成协调——前提是它们都遵守约定,主动加锁。
关键点在于:锁绑定的是文件描述符(fd),不是文件路径;但只要所有进程都通过 os.OpenFile 打开同一个文件并对其 fd 调用 syscall.Flock,内核就能识别冲突。
- 必须用
os.O_CREATE | os.O_RDWR打开文件,只读或只写模式可能导致锁失败(尤其某些内核版本) -
defer f.Close()是安全释放锁的推荐方式,避免忘记syscall.Flock(fd, syscall.LOCK_UN) - 锁是 advisory 的——不拦截未加锁的写操作,所以“所有写方都得加锁”是前提,不能只有一方守规矩
- Windows 不支持
syscall.Flock,若需跨平台,改用golang.org/x/sys/unix.Flock(仅限 Unix-like 系统)或第三方库如github.com/gofrs/flock
LOCK_EX 和 LOCK_SH 怎么选?
绝大多数资源抢占场景(如写日志、更新配置、生成唯一 ID 文件)需要排他性,直接用 syscall.LOCK_EX。只有当你明确需要并发读、且读操作本身不修改文件时,才考虑 syscall.LOCK_SH。
常见误区是以为 “多个进程读同一个配置文件” 就该加共享锁——其实没必要:读操作本身不破坏一致性,加 LOCK_SH 反而增加系统调用开销,还可能被其他进程的 LOCK_EX 阻塞。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
LOCK_EX:写入前必须获取,阻塞所有其他LOCK_EX和LOCK_SH -
LOCK_SH:允许多个共存,但会阻塞后续LOCK_EX请求 - 混用要小心:一个进程持
LOCK_SH,另一个进程尝试LOCK_EX会被挂起,直到所有LOCK_SH释放 - 没有“升级锁”机制:不能从
LOCK_SH直接升为LOCK_EX,必须先LOCK_UN再重试LOCK_EX
非阻塞锁 + 超时控制怎么写?
生产环境不能让一个 goroutine 无限等待锁,尤其是高并发写入场景。用 syscall.LOCK_NB 实现非阻塞尝试,配合 time.After 或重试计数,能快速失败或降级。
注意:syscall.Flock 本身不支持超时参数,只能靠外层控制重试逻辑。错误码 syscall.EWOULDBLOCK 表示当前锁被占用,不是系统错误。
- 典型写法:
err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) - 遇到
syscall.EWOULDBLOCK时,可选择:time.Sleep(50 * time.Millisecond)后重试,或直接返回错误、写入临时文件、走备用存储路径 - 不要在循环里高频轮询
Flock,容易打满 CPU;建议指数退避(如 10ms → 20ms → 40ms) - 如果业务允许“最多等 1 秒”,就用
for i := 0; i + <code>time.Sleep(50ms),比selectwithtime.After更易测试和调试
为什么 sync.Mutex 完全不管用?
sync.Mutex 只在单个进程内有效,对跨进程资源争抢毫无作用。哪怕你把 *os.File 包进结构体、再用 mu.Lock() 锁住这个变量,也只阻止其他 goroutine 修改该指针值,完全拦不住它们调用 file.Write() —— 因为底层还是往同一个 fd 写数据。
更隐蔽的坑是:有人把 os.OpenFile 结果存成全局变量,多个 goroutine 复用同一个 *os.File,再配 sync.Mutex,以为万事大吉。结果发现偶尔写乱——根本原因是 fd 在进程间不共享,锁只在本进程 goroutine 间起作用,跨进程照样撞车。
-
sync.Mutex/sync.RWMutex保护的是内存变量,不是文件 I/O - 分布式锁(Redis、etcd)适合跨机器场景,但引入额外依赖和延迟,单机多进程优先用
flock - 真正要防的是“多个进程往同一文件路径写”,不是“多个 goroutine 往同一变量写”
- 如果必须用内存锁,那说明你没真正面对跨进程问题,只是误判了竞争边界
文件锁的生效前提是所有参与者都打开同一路径、都调用 Flock、都不绕过锁直接写——少一个环节,整个机制就失效。最容易被忽略的不是代码怎么写,而是部署时有没有确保所有服务实例都加载了同一套加锁逻辑。

















