☰
Shell脚本防重复执行:从PID锁到flock的可靠方案与实践
2026/10/5 11:21:54 网站建设 项目流程

我先打个招呼:这篇稿子不是教科书,是我在真实环境里被坑过之后总结出来的。你如果写过cron定时任务、搞过自动化测试脚本、维护过后台守护进程,大概率遇到过这样一种诡异现象——脚本明明只该跑一次,结果却同时在系统里蹦出好几个实例。轻则日志打架、数据重复处理,重则直接把数据库写崩。没错,说的就是Shell脚本重复执行问题。今天这篇内容的核心只有一件事:怎么在脚本启动时可靠地检测“自己是不是已经在跑了”,并且用最稳妥的方式把它拦截下来。

我会把PID文件、pgrep进程匹配、flock文件锁、mkdir原子锁这些方案全部过一遍,结合定时任务、全自动测试脚本、后台跑批这些实际场景,给出可以直接抄走的模板和参数依据,最后还会拆几个我踩过的坑。无论你是只写过几行Shell的新手,还是常年维护线上脚本的老手,都应该能从这里拿到点有用的东西。

1. 为什么要防重复执行:先看看脚本重复跑会捅多大娄子

1.1 重复执行造成的三种典型事故

先说日志混乱,这是最轻的一档。脚本每跑一次就把同一批数据写一遍,日志文件里同一个任务出现好几条相同记录,排查问题的时候根本分不清哪条是哪次执行产生的。到了排障环节,你对着日志找半天,最后发现是被重复执行给刷花了,那种感觉特别浪费时间。

数据错乱就要严重一些。比如一个设备老化测试脚本,它会按批次记录测试结果,如果两个实例同时在写同一个结果文件,要么互相覆盖,要么在文件末尾交错追加,整个结果表直接废掉。再比如一个自动备份脚本,两个实例同时在打包同一目录,最后生成的压缩包可能是坏的、不完整的。

最要命的是资源耗尽。脚本里如果跑的是批量处理任务,每个实例都会申请内存、占用CPU、打开文件句柄。一个实例跑得好好的,突然又冒出两三个,系统负载直接飙升。我在一台4核8G的机器上遇到过这种事情,一个没做防重入的同步脚本被cron调度漏跑补跑,结果同时起了四个实例,CPU直接打满,连SSH连上去都要卡好几秒。

1.2 最容易踩雷的场景清单

我整理了一下,以下这些场景特别容易踩重复执行的雷:

  • cron定时任务。任务执行耗时超过了调度间隔,上一次还没跑完,下一次调度又来了。
  • 手动补救。看到脚本卡住了或者失败了,下意识重新跑一遍,结果旧进程其实还在后台运行。
  • 自动化触发链条。脚本A调用脚本B,脚本B又调用脚本C,中间某一环重复出发,整条链跑了两遍。
  • 用户重复点击/重复提交。比如内部的Web管理面板上有个“执行脚本”按钮,用户连点两次。
  • 开机自启。系统启动时通过rc.local或systemd拉起脚本,同时用户在登录后手动执行了一次。

针对性说一句:最常见的其实是cron场景。因为很多人的cron只设置了执行时间,根本没有考虑任务耗时浮动。比如设置成每5分钟跑一次,正常情况下跑3分钟,某次数据量大跑了6分钟,下一轮调度直接又起了新实例。

2. 方案一与方案二:pgrep进程匹配和PID锁文件

2.1 pgrep检测:能跑通,但很脆

先说最直观的思路:脚本启动时检查系统里有没有自己的进程。用命令行的方式,通常是pgrep加脚本名去匹配。我见过很多初版脚本这么写:

#!/bin/bash if pgrep -f "sync_data.sh" | grep -v $$ > /dev/null 2>&1; then echo "脚本已在运行,退出" exit 1 fi # 下面是实际任务

这个方案的核心逻辑是:用pgrep列出所有命令行里包含sync_data.sh的进程PID,然后把自己这个进程排除掉,如果还剩下其他PID,说明有别的实例在跑。

问题非常明显。第一,grep -v $$只排除了当前shell进程,如果你的脚本是通过bash脚本文件方式运行的,实际对应的进程可能是/bin/bash /path/to/sync_data.sh,当前PID并不代表脚本主体的PID,容易误判。第二,如果另一个用户用绝对路径运行脚本,或者通过符号链接运行,命令行里的字符串和你pgrep匹配的字符串对不上,漏检。第三,如果系统里有别的脚本也调用了相同的关键字,莫名其妙就误判了。第四,pgrep匹配的字符串里包含脚本路径,一旦路径改了,老实例没退出,新实例照样能跑。

所以这个方案我只能说“演示可以,生产环境别用”。它最大的问题是判别逻辑太依赖命令行文本,而命令行文本是可以被各种因素改变的。用这种方式来防重入,本来就是一种脆弱的做法。

2.2 PID锁文件:把实例身份固化下来

既然靠进程名匹配不可靠,自然的思路是:自己在脚本里记录一个PID文件。原理很简单,脚本启动时先检查PID文件是否存在,如果存在就读取里面的PID,然后检查这个进程还活着没。如果活着,直接退出;如果死了,说明是上次遗留的脏文件,删除后重新创建。

我整理了一个比较标准的模板:

#!/bin/bash SCRIPT_NAME="sync_data" PID_FILE="/tmp/${SCRIPT_NAME}.pid" # 检查PID文件 if [ -f "${PID_FILE}" ]; then OLD_PID=$(cat "${PID_FILE}") if kill -0 "${OLD_PID}" 2>/dev/null; then echo "[错误] 脚本已在运行,PID=${OLD_PID}" exit 1 else echo "[警告] 检测到残留PID文件,清理" rm -f "${PID_FILE}" fi fi # 创建PID文件 echo $$ > "${PID_FILE}" chmod 644 "${PID_FILE}" # 退出时清理PID文件 trap 'rm -f "${PID_FILE}"' EXIT # 下面是实际业务逻辑 echo "开始执行任务,PID=$$" sleep 300

这个方案里有两个技术点值得说。第一个是kill -0的用法,它不会给进程发任何信号,只用来检查进程是否存在以及是否具有发送信号的权限。如果进程存在且你有权限,返回0;进程不存在返回1。这比用ps -p $PID去过滤要快,而且不依赖操作系统对进程名的解析。

第二个是trap ... EXIT。这个很关键,它的作用是脚本无论正常结束、被中断、还是被kill掉,都会在退出时执行清理命令。我在模板里用的是删除PID文件,避免下一次运行时读到残留的脏文件。

但也必须指出它的缺陷。如果脚本在执行中途被kill -9强杀,EXIT陷阱不会生效,PID文件会残留。下次运行脚本时,用kill -0去检查那个PID——如果PID恰好被系统其他进程复用了,脚本就会误判“已有实例在运行”,拒绝执行。这就是所谓的谬误锁死。

2.3 两种方案各自的适用定位

两者对比起来是这样:

方案优点缺点适合场景
pgrep进程匹配实现最快、不用管理文件匹配规则脆弱、误判率高一次性临时脚本、机器上无其他脚本夹杂
PID锁文件能防常规重复、逻辑透明kill -9会产生脏文件、PID复用会误判可管控生命周期、不使用强杀手段的内部脚本

我个人的经验是:如果只是自己机器上跑的一个临时脚本,用pgrep也不是不行,毕竟省事。但如果你写的脚本是要进cron、要长期跑、要给别人用的,就别用这两个方案冒险。换用文件锁。

3. 王者方案:flock文件锁和mkdir原子锁

3.1 flock为什么值得当首选

flock是Linux内核提供的文件锁工具,它锁定的是一个文件描述符。简单理解就是这样:多把钥匙,但只存在一把能打开同一扇门的锁。它的脾气很直。

flock的核心用法是:

flock [选项] <锁文件> [命令]

它的优势非常明显。第一,锁是内核维护的,进程退出后锁自动释放,不会像PID文件那样留下脏文件。第二,判断是否加锁是原子操作,不存在两个进程同时加锁成功的情况。第三,它不需要维护PID文件,不需要检查进程状态,天然就比前面两个方案可靠。

这里说的原子性很重要,我展开解释一下。所谓原子,就是指一个操作在系统中要么完全执行、要么完全不执行,中间不会有其他进程插入。比如创建文件、修改文件内容这两个动作就做不到原子,两个进程完全可以同时读写插队。而flock加锁是一个系统调用,内核保证同一时间只能有一个进程获得锁。

3.2 可直接抄走的flock模板

我最常用的写法是这样的,你们可以直接保存使用:

#!/bin/bash LOCK_FILE="/tmp/my_task.lock" # 打开锁文件,关联到文件描述符9 exec 9>"${LOCK_FILE}" # 尝试获取排它锁(非阻塞模式) if ! flock -n 9; then echo "[$(date '+%F %T')] 已有脚本实例在运行,本次退出" exit 1 fi # 运行锁内的业务逻辑 echo "[$(date '+%F %T')] PID=$$ 开始执行任务" sleep 30 echo "[$(date '+%F %T')] 任务执行完毕"

解释一下每一行的作用。exec 9>"${LOCK_FILE}"表示以读写方式打开锁文件并绑定到文件描述符9,这一步很关键,因为之后flock锁的是这个文件描述符对应的文件。flock -n 9表示对文件描述符9上的文件加排它锁,-n表示非阻塞,如果加锁失败立即返回,不会傻等。

这段代码真正实现了“检测是否已在执行”的目标:如果前一个实例还在跑,锁被占用,flock -n 9返回非0,脚本就退出。如果前一个实例已经结束,内核释放锁,新实例顺利加锁进入任务。

还有一点值得提:为什么用exec 9>,而不是直接在命令行里写flock命令?因为我们需要的是“在整个脚本生命周期内持有锁”,而不是只锁住某一条子命令。通过exec把锁关联到脚本自身的文件描述符上,这个文件描述符在脚本退出前都是打开的,锁也一直有效。脚本结束或进程被杀,内核自动清理文件描述符,锁自然释放。

3.3 兼容性升级:带等待时间的flock变体

上面模板是“发现已有实例就立即退出”。但有些场景我们希望脚本等待一会儿,等前一个实例跑完再继续。比如数据同步任务,上一次运行快结束时,下一次调度来了,与其放弃不如排队。

这种场景把flock -n改成flock即可:

exec 9>"${LOCK_FILE}" flock 9 || { echo "无法获取锁" exit 1 }

不带-n的flock会一直阻塞等待,直到前一个实例释放锁。但要注意:阻塞等待通常是有风险的。如果前一个实例卡死、锁一直不释放,新实例会无限等下去,反而变成一个新的“卡死进程”。

更稳妥的做法是设置等待上限。flock本身没有超时参数,但可以通过shell脚本实现有限期等待:

LOCK_FILE="/tmp/my_task.lock" exec 9>"${LOCK_FILE}" # 尝试加锁,最多等60秒 for i in $(seq 1 60); do if flock -n 9; then break fi if [ "$i" -eq 60 ]; then echo "[错误] 等待锁超时,退出" exit 1 fi sleep 1 done

这样脚本最多等60秒,拿不到锁就放弃。这个逻辑很实用,尤其适合cron任务——它既能避开重复执行,又不会无限期阻塞导致任务堆积。

3.4 mkdir原子锁:不依赖flock的兜底方案

有些特殊环境里没有flock命令?虽然现在多数Linux发行版都自带了flock,但万一你在的是极简环境、busybox环境,怎么办。有个经典替代方案:利用mkdir命令的原子性做锁目录。

mkdir的原子性在于:同一目录下创建同一个子目录,只能成功一次。两个进程同时执行mkdir /tmp/mylock,只有一个会成功返回0,另一个会返回“目录已存在”。这就天然实现了互斥。

模板如下:

LOCK_DIR="/tmp/my_task.lockdir" if ! mkdir "${LOCK_DIR}" 2>/dev/null; then echo "已有脚本实例在运行,退出" exit 1 fi trap 'rmdir "${LOCK_DIR}"' EXIT # 业务逻辑 echo "开始执行任务" sleep 30

这套方案和PID锁文件比,明显更可靠。它同样存在kill -9后锁目录残留的问题,但是重试逻辑更好处理——因为目录的存在并不依赖于某个PID是否还活着,你只要判断“目录在且里面有活的PID记录”就清理,或者干脆通过目录内PID文件判断。当然,最朴素的兜底逻辑是:检查目录是否存在,再检查目录里的PID文件对应的进程是否还活着,如果进程死了就自动清理目录。

这就是为什么我喜欢用mkdir锁目录当备选方案的原因:它几乎不依赖任何外部命令,mkdir、rmdir是每个系统都有的。

3.5 四种方案横向对比,直接告诉你选哪个

把前面所有方案摆在一张表里看,这个选择就非常清楚了:

方案防重复可靠性防脏文件依赖命令推荐度
pgrep进程匹配低无需文件pgrep不推荐
PID锁文件中低kill/ps临时脚本可用
flock文件锁高高flock首选
mkdir锁目录中高中mkdir/rmdir备选

我自己的选用原则很简单:能用flock就用flock,不能用flock就用mkdir锁目录,PID文件方案只用于纯自用脚本。

4. 真实场景实战:定时任务、全自动老化测试脚本的防重入设计

4.1 cron定时任务的正确写法

定时任务应该是最需要防重复的场合。如果任务本身跑得很快(几秒内完成),重复执行的概率很低,可以不管。但如果任务耗时可能超过调度间隔,就必须处理。

我这里给出一套完整的cron防重入写法。它把脚本加锁逻辑和任务本体分开封装:

先写一个公共的加锁函数文件,比如/opt/scripts/lib_lock.sh:

#!/bin/bash # 用法: acquire_lock <lock_name> # 返回值: 0表示获取锁成功,非0表示失败 acquire_lock() { local lock_name="$1" local lock_file="/tmp/${lock_name}.lock" exec 9>"${lock_file}" if ! flock -n 9; then echo "[$(date '+%F %T')] 获取锁失败,已有实例在运行" return 1 fi return 0 } release_lock() { # 释放锁其实可以不做,文件描述符9在进程退出时自动关闭 # 这里显式关闭是为了代码可读性 exec 9>&- }

然后在具体任务脚本中引用:

#!/bin/bash source /opt/scripts/lib_lock.sh if ! acquire_lock "hourly_backup"; then exit 0 fi # 业务逻辑:全量备份 rsync -av /data/ /backup/data/

最后在crontab里配置:

30 * * * * /opt/scripts/hourly_backup.sh >> /var/log/backup.log 2>&1

这套结构的好处是清晰:加锁逻辑从业务脚本里抽离出来,多个脚本可以共用同一套函数,锁文件名就是业务名,一眼能看出来哪个锁对应哪个任务。

4.2 设备老化测试全自动执行脚本的防重复设计

网上那些热度很高的“设备老化测试全自动执行脚本”,其实就是后台跑批+数据处理+日志上报的组合体。这类脚本有个特点:测试时间长、需要持续监控、失败要自动重试,如果重复执行,两台设备同时写结果,数据直接乱套。

我设计过一个类似的脚本,它的防重复策略是这样做的:

第一层,flock锁保证同一时间只有一台“总控进程”在跑。第二层,总控进程内部记录每个测试设备的状态,用锁文件+状态文件双重判断。第三层,每次写入测试结果前,再校验一下总控进程是否还是自己——这属于双保险。

下面是一个精简后的框架:

#!/bin/bash LOCK_FILE="/tmp/aging_test_controller.lock" STATUS_FILE="/tmp/aging_test_status" DEVICE_LIST="/opt/config/devices.txt" # 总控进程只允许一个 exec 9>"${LOCK_FILE}" if ! flock -n 9; then echo "[$(date '+%F %T')] 已有老化测试总控在运行,退出" exit 1 fi # 记录总控PID,供子脚本查验 echo $$ > "${STATUS_FILE}" # 逐台设备执行测试 for device in $(cat "${DEVICE_LIST}"); do echo "[$(date '+%F %T')] 开始测试设备: ${device}" # 这里建议再套一层flock,防止同一台设备被并发测试 DEVICE_LOCK="/tmp/aging_test_${device}.lock" exec 8>"${DEVICE_LOCK}" if ! flock -n 8; then echo "[警告] 设备 ${device} 已在测试中,跳过" continue fi # 执行单台设备的测试逻辑 bash run_test.sh "${device}" exec 8>&- done

这套设计看起来不复杂,但解决了一个很隐蔽的问题:总控不重入,不代表子任务不重入。如果一台设备刚好在上一个测试周期里卡住,新周期的总控又来了,单台设备的锁就把重复的测试挡掉了。多级锁的思维在这个场景里很实用。

4.3 超时处理与强制解锁策略

脚本在执行中卡死的情况谁都会遇到,锁一直占着,后续任务全部被挡住。这时候怎么处理?我分几步讲。

第一步,先看锁文件被哪个PID持有。flock本身不直接记录PID,但你可以通过fuser命令查:

fuser -v /tmp/my_task.lock

输出里能看到占用该文件的进程PID和用户。第二步,判断该进程是否还健在。用ps -p $PID -o pid,etime,cmd看下它跑了多久、在干什么。第三步,视情况处理。如果确认是死锁或者卡死的僵尸进程,可以强杀或者直接用fuser -k /tmp/my_task.lock把持有锁的进程干掉。

注意,fuser -k是一个比较粗暴的命令,它会杀掉所有打开该文件的进程。用之前必须确认没有其他重要进程在读写锁文件。现实里锁文件通常只被脚本自身打开,风险可控。

还有一点要提醒:强制解锁后,锁文件可以保留也可以删除。删除后下一次脚本重启会重新创建锁文件,没问题。但如果脚本正在运行,你在它退出前先把锁文件删了,就会产生一个新问题——后续新脚本可以创建新锁文件并成功加锁。这时候旧实例和新实例就可能同时存在。所以正确顺序一定是:先确认进程已死,再删锁文件。如果进程还活着,你删了锁文件就是在制造新的竞态。

这也是flock方案比PID文件方案优越的地方:哪怕你不删锁文件,只要持有锁的进程死掉,内核自动释放锁。删不删文件根本无所谓。推荐直接留着锁文件,里面没东西,不影响任何逻辑。

4.4 开机自启场景的特殊处理

开机自启也是重复执行的重灾区。rc.local里启动一次,systemd里又启动一次,用户登录后手动再来一次,三次叠加。

systemd场景下有一个细节:如果你的脚本在启动时设置了flock,但是systemd服务配置了Restart=always,服务崩溃后会被systemd再次拉起。此时如果锁还在,新实例会直接退出,然后systemd继续尝试拉起,形成一个“起来就退出”的死循环。这种问题我碰到过。

解法有两个思路。思路一:给flock加等待时间,让新实例等待旧实例结束。思路二:在脚本里跳过已经被systemd管理的场景,简单判断一下环境变量:

if [ -n "$SYSTEMD_UNIT" ]; then echo "由systemd管理,跳过自锁" exit 0 fi

当然这不算通用方案,但确实现实里有人这么处理。我更推荐的还是让systemd自己管理单实例:直接在service文件中加Type=exec或Type=notify,配合StartLimitIntervalSec、StartLimitBurst等方式控制服务重启行为。脚本层的锁只作为最后一道防线。

5. 常见坑点与排查实录

5.1 用了flock还是重入?检查你是不是加锁了子进程

我调试过的最常见案发现场是这样的:脚本明明加了flock,结果还是起了两个实例在后头跑。查了半天才发现,业务逻辑里调用了bash run_task.sh或者nohup python worker.py &,这些子进程自己不带锁,于是父进程虽然被锁住了,但是子进程在后台独立跑,重复照旧。

所以我想强调一点:flock锁的粒度是“进程实例”,不是“业务逻辑”。如果你的脚本会派生子进程,尤其是后台进程,一定要保证子进程在业务逻辑上也加了防重入。否则,你防的只是父进程重复,挡不住子进程叠罗汉。

实际操作中最好的习惯是:把整个业务逻辑抽成一个函数或者一个单独的脚本,所有外层调用都指向同一个入口,锁加在这个统一入口上。

5.2 脚本软链接和调用方式会骗过锁?其实不会,但会骗过日志

锁文件方案本身和脚本路径无关,按说软链接不影响。但我遇到过一种场景:同一个脚本通过软链接产生两个名字,两个不同的cron任务分别调用,它们用的锁文件却是同一个,结果第二个任务永远在退出。

这种情况的本质是:锁名的设计太粗粒度。两个任务虽然在执行同一段代码,但语义上可能是不同业务,不应该共用一个锁。解决方法是:根据业务来区分锁名,不要按脚本名来。锁名应该是任务的身份标识,什么业务就用什么锁名。

举个例子:

LOCK_FILE="/tmp/$(basename "$0").lock" # 按脚本名 LOCK_FILE="/tmp/$TASK_NAME.lock" # 按业务名

我后来的习惯是:在脚本头部定义一个明确的锁名常量,而不是从路径动态生成。代码可读性和可维护性都会更好。

5.3 跨用户运行时,锁文件权限惹的祸

在cron里用root跑了一次脚本,锁文件是root创建的,放到了/tmp/xxx.lock。后来又用普通用户手动执行同一个脚本,结果flock对锁文件没有写权限,直接报错。这个不算大坑,但排查起来会卡住新手。

解决方案有两个。第一个是选择共享目录+固定权限,比如用/var/lock/,创建锁文件时touch并chmod 666。第二个是坚持“谁的脚本用谁的锁目录”,不同用户使用不同的锁目录。/tmp其实不建议放跨用户共享的锁文件,因为/tmp默认权限是1777,但文件本身的属主权限会限制其他用户打开。

最稳妥的写法是:锁文件路径默认放在/var/lock/下,创建后统一设置权限0644,脚本里获取锁之前先确保文件存在。当你用普通用户fork出一个需要与其他用户共享的任务时,再用/var/lock+chmod 777的方式。

5.4 flock与NFS文件系统的那些事

如果你的脚本运行在NFS挂载的目录上,并且锁文件也放在NFS上,这里有一个要注意的点。传统的flock(POSIX记录锁)在NFS上并不总是可靠,因为NFS协议对flock的支持各发行版不一样。常见的现象是:两个节点上同时跑同一个脚本,锁却不起作用,照样重入。

正确做法是:锁文件放到本地文件系统,业务数据放NFS。比如锁放/var/lock/,数据放/mnt/nfs/data/。这样锁的可靠性由本地内核保证,不依赖NFS的锁语义。如果你的业务必须跨节点防重入,那flock就不够用了,需要用到分布式锁。那些情况已经超出本文讨论范围,这里就不展开。

5.5 实战排查流程:5分钟定位脚本重复执行

最后给一套手把手的排查流程,你在现场照着走就行。

第一步,确认同时在跑哪些相关进程。用这个命令看到所有与任务相关的进程:

ps -ef | grep -E "task_name|script_name" | grep -v grep

第二步,确认锁文件被谁占用:

fuser -v /tmp/task.lock

如果没有输出,说明锁文件虽然存在但没进程占用,属于残留锁。如果有进程占用,看到的是PID和用户。

第三步,判断锁文件创建时间与实际进程启动时间的对应关系:

stat /tmp/task.lock ps -p <PID> -o lstart

如果锁文件时间在前、进程启动时间在后,说明进程正常持有锁。如果锁文件时间在后、进程时间在前,说明锁被后来的进程重新创建,可能存在两个实例。

第四步,根据排查结果选择处理方法。残留锁直接删除;有进程占用则先查进程状态再决定强杀还是等待。

这套流程是我日常排查问题的路线,基本不用花太长时间就能定位到底哪一层出了问题。

6. 我踩过几次坑之后的固定习惯

写到最后,分享几个我现在写脚本时定下来的固定习惯,谈不上标准答案,但确实帮我省了很多事。

第一个习惯:锁文件名永远不用$$或随机数,直接用业务名称常量。因为锁名的可读性和唯一性比“随机性”重要得多。第二个习惯:flock永远配合exec使用,让锁跟着当前进程走,而不是锁一条子命令。exec 9>"${LOCK_FILE}"这行代码在模板里看着不起眼,实际是最关键的一步。第三个习惯:所有脚本的锁文件统一放在一个目录,我用的是/var/lock/下按业务名建子目录,配合cron和systemd都很自然。

第四个习惯,也是我觉得最值得说的:不管用什么方案,我都会在脚本开头打一行日志,记录PID、锁文件路径、加锁结果。真的,加锁成功还是失败、哪个PID成功加锁,这些信息一旦写进日志文件,后续排查重复执行问题能节省大量时间。

LOG_FILE="/var/log/task_lock.log" echo "[$(date '+%F %T')] PID=$$ 尝试加锁 ${LOCK_FILE}" >> "${LOG_FILE}"

别小看这行日志。等到哪天真出了线上问题,你敢说你能从一堆进程里准确分清谁先谁后、谁锁谁没锁?有日志在,几分钟就定位完;没日志,可能要在机房蹲半天。这算是反复踩坑后形成的肌肉记忆吧。

回到主题上,Shell脚本防重复执行这件事,起步门槛并不高,但想要做得稳、应对各种特殊场景,确实需要花点心思。我推荐的第一选择就是flock加exec组合,它是我目前用下来最省心、最可靠、最不依赖运行环境的方案。设备老化测试脚本、cron定时任务、备份脚本、同步脚本,凡是需要单实例运行的,都可以直接用上面给你的模板改个锁名就上线。万一遇到flock都没法解决问题的情况,再回头检查一下是不是锁粒度不对、跨用户权限不对、或者文件系统不支持,按第5章的排查流程一步步过一遍,基本都能有个结论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询