用 flock 给 cron 脚本加并发锁:防重复跑的最小写法

2026-10-06 · 喵窝资源铺

场景:定时任务跑得比上一轮慢——爬虫卡住、网络重试、大文件处理——下一轮 cron 又启动了,两个实例同时写同一个文件/同一个队列,轻则数据错乱,重则互相覆盖。网上常见答案是「脚本里用 PID 文件判断」,但 PID 文件方案要处理进程死了没删文件、误杀别人 PID 等一堆边界。Linux 自带的 flock 是更省事的解法:内核级文件锁,进程死了锁自动释放,零残留。一行改完 crontab:

*/10 * * * * flock -n /tmp/myjob.lock /path/to/your_script.sh

拿到不了锁就静默退出(-n = nonblocking),上一轮还在跑就跳过本轮。这行几乎不需要解释,下面是实跑验证的细节和两个易踩的坑。

实跑验证:锁真的挡得住吗

方法:父进程用 Python fcntl 持锁,子进程用 flock 命令行尝试拿同一把锁。

# 父进程持锁时
$ python3 -c "import fcntl,subprocess; f=open('/tmp/t.lock','w'); fcntl.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB); print(subprocess.run(['flock','-n','/tmp/t.lock','-c','echo inside'],capture_output=True).returncode)"
1            # 子进程拿不到锁,rc=1,'inside' 没有输出

# 释放后再试
... fcntl.flock(f,fcntl.LOCK_UN) ...
0            # 子进程拿到锁,输出 'inside'

结论:fcntl(代码)与 flock(命令行)锁的是同一把内核锁,混用没问题——比如主脚本是 Python 用 fcntl,包装层是 shell 用 flock,互斥依然生效。

坑 1:Python 里重开同一个文件不会撞锁

Python 的 fcntl.flock 是按打开的文件描述(open file description)加锁的,不是按文件路径。同一个进程里 open() 同一个文件两次、分别 flock,两把锁互不冲突(都能拿到)。也就是说 fcntl 方案只对「不同进程」互斥——这正好是 cron 场景的需求,但如果你的脚本内部开了多线程又各自 open 同一个文件,fcntl 挡不住线程之间的并发,得换 threading.Lock。

坑 2:flock 拿不到锁是「退出码 1」,别当脚本失败

flock -n ... cmd 拿不到锁时退出码是 1,cron 的邮件通知/监控可能把它当失败告警。如果这个任务是「跑不到就算了」型,直接忽略;如果需要区分「真的崩了」和「被锁跳过了」,用退出码 255 通道:flock -n -E 0 /tmp/myjob.lock cmd(-E 指定拿不到锁时的退出码,0 表示视为正常)。

坑 3:锁文件放 /tmp,别放工作目录

工作目录可能被同步盘/备份工具扫描,或随部署被整个删掉重建,锁文件一并消失。放 /tmp 或 /var/lock,路径固定、权限干净,也不用写清理逻辑(flock 锁在进程退出时自动释放,文件残留无害,下次直接复用)。

什么时候不用 flock

  • 需要跨机器互斥(多台服务器跑同一个任务)——flock 只锁本机,得用 Redis SETNX 或数据库锁
  • 需要排队而不是跳过(第二个实例等第一个跑完再跑)——去掉 -n 改成 flock /tmp/myjob.lock cmd,默认阻塞等待
  • 锁要跨容器/跨用户共享——flock 依赖同一内核,容器内各自 /tmp 时锁文件路径要挂同一个卷
  • 这类小坑我们帮你踩

    定时任务、批处理脚本、自动化运维这类活,跑起来之后的坑(锁、重试、日志轮转)比写代码本身多。喵窝资源铺提供脚本代写+调试服务,交付前在本机实跑验证。
    👉 脚本代写服务(¥19.9 起) | 更多实测工具清单

    本文所有代码与命令均在 Linux 实机验证,输出为真实运行结果。