Linux/Bash:用 flock 阻止定时任务重叠执行

7 次浏览1 条回复

同一个定时任务如果上一次还没结束,下一次又被调度,可能重复写文件或同时占用外部资源。Linux 上可以在脚本入口用 flock 对文件描述符加非阻塞锁;拿不到锁时立即退出。

环境:Linux;Bash 4.1+;util-linux 提供的 flock;coreutils 提供的 install

创建 single-run.sh

#!/usr/bin/env bash
set -Eeuo pipefail

runtime_dir="${XDG_RUNTIME_DIR:-$HOME/.cache}/report-job"
install -d -m 700 -- "$runtime_dir"

exec {lock_fd}>"$runtime_dir/run.lock"
if ! flock -n "$lock_fd"; then
  printf 'another instance is already running\n' >&2
  exit 75
fi

printf 'started: pid=%d\n' "$$"
sleep 5
printf 'finished: pid=%d\n' "$$"

赋予执行权限,并启动两个实例验证:

chmod +x single-run.sh

./single-run.sh &
first_pid=$!
sleep 0.2

set +e
./single-run.sh
second_status=$?
set -e

wait "$first_pid"
printf 'second status=%d\n' "$second_status"

第一个实例持锁期间,第二个实例会输出提示并以 75 退出;第一个实例结束后,文件描述符由内核关闭,锁自动释放。锁文件本身可以继续存在,互斥关系取决于打开文件上的锁,而不是靠删除锁文件实现。

把锁目录放在当前用户可写且权限受控的位置,可以避免多个无关用户共享同一个可替换的临时路径。若任务需要排队等待而不是快速退出,可将 flock -n 改为 flock -w 30,最多等待 30 秒。

补一个锁作用域的边界:这里的 $XDG_RUNTIME_DIR$HOME/.cache 都按 Unix 用户隔离,因此示例保证的是“同一账号内单实例”。如果同一任务可能由两个系统账号触发,即使锁文件名相同,也会落到不同目录,仍然可能并发。

若保护的是主机级共享资源,可以在部署阶段预建统一锁目录。环境:Linux、GNU coreutils;假设服务账号 reportjob 已存在。

sudo install -d -o reportjob -g reportjob -m 0750 /run/lock/report-job

脚本中再固定使用:

runtime_dir=/run/lock/report-job
exec {lock_fd}>"$runtime_dir/run.lock"

所有触发入口应以该服务账号运行;如果必须由不同账号触发,则需要预先设计共享组权限。这样锁的范围才与被保护资源的范围一致。