
定时任务、备份脚本和本地索引程序经常用一个空文件表示“正在运行”。但文件存在不等于锁仍被持有,检查后再创建也存在竞争窗口。可靠互斥的关键,是让内核管理锁的归属和释放,而不是自己猜测另一个进程是否活着。
锁住的不是路径名
flock 先打开文件,再给对应的打开文件加共享锁或独占锁。路径只是进程找到该文件的入口;真正需要保留的是文件描述符。持锁进程退出,或相关描述符全部关闭后,内核会自动释放锁,因此崩溃后通常不需要“删除陈旧锁”才能恢复。
锁文件可以一直留在磁盘上。删除并重建它反而危险:旧进程可能锁着已被删除的 inode,新进程却锁住了同一路径下的新文件,两个进程便同时进入临界区。锁文件应放在权限受控、生命周期明确的目录中,并保持稳定。
用 flock 保护 Shell 任务
下面把描述符 9 的生命周期覆盖整个任务,并最多等待十秒:
1 |
|
flock -n 9 适合“已有实例就跳过”,flock -w 10 9 适合短暂等待。无论哪种方式,都应让拿锁失败成为可观察的结果:返回非零退出码,并记录任务名和等待时间。不要在获取锁后启动后台任务便立刻退出,否则父进程关闭描述符时,保护范围可能提前结束。

flock 与 fcntl 怎么选
| 需求 | 更合适的选择 | 注意点 |
|---|---|---|
| 整个脚本只运行一个实例 | flock |
简单,便于在 Shell 中使用 |
| 多个读者、单个写者 | 共享/独占 flock |
所有参与者必须遵守同一协议 |
| 只锁文件的一段字节范围 | fcntl 记录锁 |
生命周期语义更复杂,需统一封装 |
| 跨主机协调任务 | 数据库锁或协调服务 | 本地文件锁通常不够 |
传统 fcntl 记录锁与进程关系紧密,还能锁定字节范围;flock 更适合“围住一整个任务”。两者的继承、重复打开与关闭语义并不相同,不要在同一协议里混用,也不要假设任意语言库实现的是同一种锁。
劝告锁不是权限墙
Linux 文件锁通常是劝告式的:只有主动获取锁的进程才会被约束,忽略协议的程序仍可直接读写文件。因此锁负责协调参与者,文件权限负责限制访问,两者不能互相替代。
锁也不负责提交完整数据。若临界区内要更新配置,应组合“先加锁、读取并计算、写临时文件、原子替换、最后解锁”。锁避免多个写者同时修改,原子替换避免读者看到半份内容;它们解决的是不同问题。
上线前验证四种失败
至少测试:两个进程同时抢锁、等待超时、持锁进程被 SIGKILL、任务抛错退出。每次都要确认最多一个进程进入临界区,失败者退出码明确,并且异常进程结束后新实例能重新取得锁。
容器多副本或网络文件系统还要额外验证:不同节点可能看不到同一块本地磁盘,网络文件系统的锁语义也取决于服务端与挂载方式。只要协调范围越过单机,就应把互斥放到所有实例共同访问、能够明确处理租约和故障的系统中。