功能说明
定时任务、数据迁移和单实例守护进程需要保证同一时刻只有一个执行者。文件锁是一种轻量方案:进程打开锁文件并申请独占锁,成功者继续,失败者立即退出。锁随文件描述符存在,进程崩溃时由内核自动释放,不会留下永久死锁。
示例先取得锁,再在仍持锁时尝试第二次获取,预期被拒绝;释放后重新获取成功。withLock 辅助函数用 defer 保证释放,适合包裹实际工作。
关键代码
OpenFile 以创建加读写模式打开锁文件,权限为零六零零,避免其他用户操作。syscall.Flock 的 LOCK_EX 表示独占,LOCK_NB 表示不等待,被占用时立即返回错误;失败分支先关闭文件避免描述符泄漏。Release 先显式解锁再关闭,两步错误都会返回。withLock 在函数返回时调用 Release,即使 work 返回错误也能解锁。
使用注意
flock 只在同一台主机内有效,跨容器或多副本部署需要分布式锁。NFS 等网络文件系统对 flock 支持不一致,使用前应确认。锁文件自身不要删除,删除后新进程会创建新文件并绕过旧锁。本示例依赖 Unix 系统调用,Windows 需要改用 LockFileEx。锁不提供公平队列,第二个进程直接失败,业务上应配合重试与退避。
验证方式
第一次获取成功并打印锁文件名。第二次获取在同一个进程中用新的描述符打开同一文件,也会被拒绝,输出提示为 true。释放后重新获取成功,输出为 true。可以同时运行两个进程,观察只有一个能进入临界区,另一个打印锁被占用。
代码示例
package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
type FileLock struct {
file *os.File
path string
}
func AcquireFileLock(path string) (*FileLock, error) {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return nil, fmt.Errorf("open lock file: %w", err)
}
if err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
file.Close()
return nil, fmt.Errorf("lock held elsewhere: %w", err)
}
return &FileLock{file: file, path: path}, nil
}
func (l *FileLock) Release() error {
if err := syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN); err != nil {
l.file.Close()
return fmt.Errorf("unlock: %w", err)
}
return l.file.Close()
}
func withLock(path string, work func() error) error {
lock, err := AcquireFileLock(path)
if err != nil {
return err
}
defer lock.Release()
return work()
}
func main() {
path := filepath.Join(os.TempDir(), "devexamples-file-lock.lock")
lock, err := AcquireFileLock(path)
if err != nil {
fmt.Println("first acquire failed:", err)
return
}
fmt.Println("locked:", filepath.Base(lock.path))
if _, err := AcquireFileLock(path); err != nil {
fmt.Println("second acquire rejected:", err != nil)
}
if err := lock.Release(); err != nil {
fmt.Println("release failed:", err)
}
reopened, err := AcquireFileLock(path)
fmt.Println("reacquired:", err == nil)
if err == nil {
_ = reopened.Release()
}
}