syscall.flock 是系统调用封装,非跨平台锁函数;linux/macos 需 os.o_rdwr、同一 *os.file、文件已存在;windows 不支持,应改用 lockfileex 或 github.com/gofrs/flock。

syscall.Flock 在 Go 中不是“加锁函数”,而是对底层系统调用的直接封装;它不跨平台,不自动重试,也不处理 fd 生命周期细节——直接用容易出错。
Linux/macOS 下用 syscall.Flock 加锁必须满足三个条件
很多人写完 os.Open 就调 syscall.Flock,结果报 invalid argument 或锁失效。根本原因是没满足内核对 flock 的前置要求:
- 文件必须以
os.O_RDWR(或至少带写权限)打开,只读打开(os.O_RDONLY)在部分内核版本下会拒绝加锁 - 必须对**同一个
*os.File实例**调用syscall.Flock,不能反复os.Open后传不同 fd —— 锁是绑定在 fd 上的,不是路径上 - 加锁前需确保文件已存在且可访问;
os.O_CREATE不足以让flock成功,它不参与原子创建逻辑
Windows 下 syscall.Flock 直接不可用
编译时不会报错,但运行时调用会返回 ENOSYS(function not implemented)。这不是 Go 的 bug,是 Windows 本身不提供 POSIX flock 语义。你看到的错误信息通常是:
undefined: syscall.Flock(编译期)或 operation not supported(运行期)
替代方案只有两个可靠选择:
- 用
golang.org/x/sys/windows调LockFileEx:支持字节范围、超时、可中断,是 Windows 原生推荐方式 - 用第三方跨平台包(如
github.com/gofrs/flock):它内部自动按平台 dispatch,Linux 走syscall.Flock,Windows 走LockFileEx,还帮你处理了 fork 后 fd 复制、信号中断重试等边界
为什么 os.OpenFile(..., os.O_EXCL|os.O_CREATE) 不是文件锁
它常被误当作锁来用,但本质是“文件存在性检查 + 原子创建”,不是同步原语:
- NFS 等网络文件系统不保证
O_EXCL原子性,多个进程可能同时创建成功 - 文件被删后锁即消失,无法感知持有者是否崩溃,容易形成“假死锁”
- 没有超时机制,也没有阻塞/非阻塞切换能力
- 它只防“创建”,不防“写入”——另一个进程仍可
os.Open已存在的文件并覆盖内容
github.com/gofrs/flock 是目前最省心的选择
它把所有平台差异和系统陷阱都收进去了,用法极简:
import "github.com/gofrs/flock"
f := flock.New("/path/to/lock")
locked, err := f.TryLock()
if err != nil {
log.Fatal(err)
}
if !locked {
log.Println("already locked by another process")
return
}
defer f.Unlock()
// do work
关键点:
-
TryLock()非阻塞,失败立即返回false,不 panic -
Unlock()安全幂等,即使重复调也无副作用 - 自动处理
fork后子进程继承锁的问题(Unix)和句柄跨线程复用(Windows) - 锁文件本身只是载体,真正起作用的是底层系统调用,所以它不依赖文件内容或命名规则
真正的难点不在“怎么加锁”,而在“锁住之后是否真能拦住所有并发写入”——这取决于所有协作方是否都遵守同一套锁协议。如果一个进程用 flock,另一个用 shell 脚本直接 echo > file,那锁就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











