示例说明
cron 里每小时跑的同步任务,上一次卡住没退出时下一次又启动——双实例同时写就是事故。flock 的锁绑定文件描述符而非文件存在性:进程退出(包括被杀)时内核自动释放锁,不存在残留死锁;-n 非阻塞模式让后来者立即知难而退,调度语义就是跳过本次而非排队。
技术要点
- 锁随进程退出由内核自动释放
- -n 非阻塞语义即跳过不排队
- 描述符与锁生命周期绑定
代码解读
exec 9> 打开锁文件并占用描述符九,flock -n 9 抢锁失败即走跳过分支——exit 0 让 cron 不报错,跳过是正常调度结果;critical_section 里即使异常退出,描述符关闭即锁释放。结束时显式关闭描述符,防止本进程派生的后台任务意外继承锁。
使用建议
锁文件路径要稳定且属专用目录。等待模式 -w 配超时防长队。锁内逻辑仍要幂等——锁挡并发不挡崩溃后重跑。
代码示例
#!/usr/bin/env bash
set -euo pipefail
readonly LOCK_FILE="/tmp/nightly-sync.lock"
readonly LOCK_WAIT=5
# 形态一:非阻塞抢锁,拿不到直接退出(调度器友好)
exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
echo "另一实例正在运行,跳过本次执行" >&2
exit 0
fi
# 形态二:限时等待锁(需要排队的场景)
# flock -w "${LOCK_WAIT}" 9 || { echo "锁等待超时" >&2; exit 75; }
echo "获得锁,PID $$ 开始同步"
critical_section() {
local started=$(date +%s)
# ... 同步主体,异常退出时锁随文件描述符自动释放 ...
sleep 2
echo "同步完成,耗时 $(( $(date +%s) - started ))s"
}
critical_section
# 拿到锁后派生的后台任务也持锁,需要先关描述符
exec 9>&-
echo "锁已释放,脚本结束"