跨平台文件锁应统一用 o_create|o_excl 创建锁文件,因其原子性可保证互斥;os.file.lock 在unix是建议性锁、windows才有效,flock在windows不工作,锁文件方案最稳定可靠。

Windows 下 flock 根本不工作
Go 标准库的 os.File.Chmod 和 os.OpenFile 不提供跨平台文件锁抽象,flock 是 Unix 系统调用,在 Windows 上直接调用会 panic 或静默失败。很多开发者在本地 macOS/Linux 测试通过,一上 Windows CI 就卡死或锁失效。
- 别依赖
syscall.Flock或第三方封装了flock的包(如github.com/gofrs/flock)来跑 Windows —— 它们内部 fallback 逻辑往往不透明,且默认行为可能不是你想要的“独占写” - Windows 原生等价的是
LockFileEx,Go 运行时通过os.File.Lock/Unlock暴露了它(从 Go 1.19 起稳定),但仅限于 *Windows*;Unix 系统上这两个方法目前 panic - 真正可移植的做法是:用
os.Create配合O_CREATE | O_EXCL创建锁文件(lockfile),靠原子性保证互斥 —— 这才是跨平台最稳的底线方案
os.File.Lock 在 Unix 上不能直接用
虽然文档说 os.File.Lock “尝试获取共享或独占锁”,但它在 Linux/macOS 上实际调用的是 fcntl(F_SETLK),而该锁是**建议性锁(advisory)**,不是强制锁(mandatory)。这意味着:只要另一个进程不主动调用 Lock,它就能绕过锁直接读写文件。
- 常见误判:看到
Lock返回 nil 就以为“锁住了”,结果并发写入时文件损坏 —— 因为对方压根没走 Go 的锁流程 - 如果你控制全部访问路径(比如全是 Go 进程 + 统一锁封装),可以谨慎用
Lock;否则必须配合文件名约定(如data.json.lock)+O_CREATE | O_EXCL锁文件 - 注意
Lock是阻塞式,超时需自己用time.AfterFunc+Unlock配合 context 控制,别裸调
用锁文件(lockfile)时,O_EXCL 是关键
创建锁文件的核心不是“写内容”,而是利用 os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) 的原子性 —— 如果文件已存在,系统直接返回 *os.PathError,错误信息里含 "file exists",这才是你判断“锁被占用”的依据。
- 不要用
os.Stat+os.Create两步走:中间存在竞态窗口,两个进程可能同时发现文件不存在,然后都成功创建 - 锁文件内容建议写入 PID + 启动时间(如
fmt.Fprintf(lock, "%d %d", os.Getpid(), time.Now().Unix())),方便 debug 时识别谁持锁、是否僵死 - 务必用
defer lockFile.Close()+os.Remove清理锁文件,但要注意:进程崩溃时锁文件残留,需加超时检测逻辑(比如读取 PID 是否还存活)
跨平台锁封装要避开 build tags 大段分支
有人为不同系统写三套锁实现,再用 //go:build windows 切换,结果维护成本飙升。其实 95% 场景下,统一用锁文件方案即可覆盖所有平台,且语义清晰。
立即学习“go语言免费学习笔记(深入)”;
- Windows 的
LockFileEx和 Unix 的flock行为差异大(比如 fork 后子进程是否继承锁、是否支持共享锁),强行统一抽象反而容易出 bug - 如果真需要性能敏感的短时内存锁(非文件级),改用
sync.Mutex或sync.RWMutex—— 文件锁从来就不是为高频争抢设计的 - 唯一值得条件编译的地方是锁文件清理:Windows 上删除正在被其他进程打开的锁文件会失败,需先确保
Close完全完成,Unix 则相对宽松
跨平台文件锁真正的复杂点不在 API 调用,而在锁生命周期管理 —— 创建、持有、释放、故障恢复,每一步都有平台相关副作用,别指望一个函数调用能兜底。










