syscall.Flock 在 Windows 上未定义,编译即报错;其依赖 POSIX flock(2),而 Windows 无此系统调用;跨平台应采用锁文件(O_CREATE|O_EXCL)方案,或按平台分治使用 x/sys/unix.Flock 与 x/sys/windows.LockFileEx。

syscall.Flock在 Windows 上根本不可用,不是“不推荐”,而是编译就报错:`undefined: syscall.Flock`。跨平台文件独占访问不能靠它兜底。
为什么 syscall.Flock 不能跨平台用
它直接调用 POSIX flock(2) 系统调用,而 Windows 没有这个接口。Go 的 syscall 包在 Windows 下压根没定义 Flock、LOCK_EX 等符号。你在 macOS/Linux 写好、测试通过的代码,一放到 Windows CI 就编译失败——不是运行时 panic,是连 build 都过不了。
更隐蔽的问题是:即使你用 //go:build !windows 条件编译绕过,Unix 上的 syscall.Flock 是建议性锁(advisory),不强制拦截未加锁的写入。只要有一个进程不走这套逻辑(比如用 shell 脚本直接 echo >> data.json),锁就形同虚设。
- Windows 下必须用
golang.org/x/sys/windows.LockFileEx - Unix 下可用
golang.org/x/sys/unix.Flock(比弃用的syscall更稳定) - 但两者 API 完全不兼容,无法统一抽象成一个函数
真正跨平台的底线方案:锁文件(lockfile)+ O_CREATE | O_EXCL
这是唯一不依赖系统调用、行为完全一致、且被 Nginx、systemd、apt 等成熟项目长期验证的方案。核心不是“锁”,而是利用 os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) 的原子性:
- 如果文件不存在 → 成功创建并返回 *os.File,表示“拿到锁”
- 如果文件已存在 → 返回
*os.PathError,Err.Error()含"file exists",表示“锁被占用” - 绝不能先
os.Stat再os.Create,中间有竞态窗口
示例关键逻辑:
f, err := os.OpenFile("/tmp/myapp.lock", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
if os.IsExist(err) {
log.Fatal("lock file exists — another instance running")
}
log.Fatal(err)
}
defer f.Close()
defer os.Remove("/tmp/myapp.lock") // 进程退出时清理
fmt.Fprintf(f, "%d %d", os.Getpid(), time.Now().Unix()) // 写 PID 和时间,便于 debug
如果你必须用原生文件锁(非锁文件),按平台分治
不要幻想一个函数通吃。必须显式区分:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Unix:用
golang.org/x/sys/unix.Flock(fd, unix.LOCK_EX),注意fd必须来自os.OpenFile(..., os.O_RDWR, ...),只读打开会失败 - Windows:用
golang.org/x/sys/windows.CreateFile打开句柄(关键要传0作 share mode,即禁止其他进程同时访问),再调windows.LockFileEx(h, windows.LOCKFILE_EXCLUSIVE_LOCK, ...) - 别碰
os.File.Lock():它在 Unix 上 panic,在 Windows 上才有效,且 Go 1.19+ 才稳定,版本兼容性差
第三方包如 github.com/gofrs/flock 内部也是平台分治,但它默认 fallback 行为可能不符合你的语义(比如非阻塞失败后是否重试),不如自己控制清晰。
锁残留和僵死进程怎么处理
锁文件方案下,进程崩溃会导致 /tmp/myapp.lock 残留。不能靠“文件存在=有活进程”做判断:
- 读取锁文件内容,解析出 PID
- 用
kill -0(Unix)或windows.OpenProcess(Windows)检查该 PID 是否还存活 - 若进程已死,且锁文件时间戳超过阈值(如 5 分钟),才自动清理并抢占
- 切勿无条件删除——可能真有长任务在跑,只是还没写完
原生锁(Flock/LockFileEx)本身由内核管理,进程退出时自动释放,但锁文件方案必须自己承担这部分运维逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










