在 Linux 服务器上,备份、文件同步和数据统计经常通过 Cron 定时执行。
如果上一轮任务还没完成,下一次计划时间就到了,同一个脚本可能同时运行多份,造成资源占用增加、文件写入冲突,甚至重复处理数据。
对于同一台服务器上的任务,可以使用 flock 文件锁控制并发,让使用同一把锁的任务避免同时执行。
本文以 Debian、Ubuntu 服务器为例,介绍如何验证锁的效果,再将它应用到已有的 Cron 任务中。
1. 为什么定时任务会重叠?
假设某个脚本每分钟执行一次,但每次需要运行90秒。
可能出现这样的情况:
| 时间 | 未加锁时的情况 |
|---|---|
| 12:00 | 第一轮开始 |
| 12:01 | 第一轮还没结束,第二轮已经启动 |
| 12:01:30 | 第一轮结束,第二轮仍在运行 |
| 12:02 | 第三轮启动,再次与上一轮重叠 |
Cron 按照时间规则触发命令,并不会自动为同一个脚本设置互斥锁。
排查时还要区分两种情况:
- 同一条任务执行时间过长: 可以考虑使用文件锁防止重叠。
- 同一个任务被重复配置: 应先检查用户 crontab、
/etc/cron.d/、管理面板等入口,删除确认多余的配置。
文件锁主要解决同时运行的问题。即使两次执行没有重叠,也可能因为重复配置而先后执行两遍。

2. 检查环境和flock命令
本文适用于单台服务器、本地文件系统上的任务。
先在服务器终端执行:
command -v flock
flock --version
Debian、Ubuntu 中通常可以看到命令路径:
/usr/bin/flock
如果没有安装,可以使用 root 用户执行:
apt-get update
apt-get install -y util-linux
安装后重新检查命令。
后面的独立演示使用 root 用户。接入正式任务时,应保留任务原本的执行用户,并确认它有权限读取脚本、创建锁文件和写入日志。
如果任务完全没有触发,可以先参考本站:
如果需要独立的测试环境,可以使用莱卡云 Linux 云服务器进行操作。
查看官网购买链接: https://www.lcayun.com
9折优惠码:bloglcayun
3. 先验证:同一个任务能否重复启动
使用两个连接到同一台服务器的终端窗口,先做一个不涉及业务数据的演示。
3.1 在第一个终端占用锁
创建演示目录:
install -d -m 700 /opt/flock-demo
随后执行:
/usr/bin/flock -n -E 75 /opt/flock-demo/task.lock \
/bin/bash -c 'echo START; /usr/bin/sleep 30; echo END'
终端先显示:
START
随后等待30秒,最后显示:
END
这里使用等待命令模拟一项正在运行的任务。
主要参数的作用如下:
| 参数 | 作用 |
|---|---|
-n | 无法立即取得锁时直接退出 |
-E 75 | 将锁冲突时的退出码设置为75 |
/opt/flock-demo/task.lock | 本次使用的锁文件 |
| 后面的命令 | 取得锁后才执行的任务 |
flock 默认使用排他锁。使用同一个锁文件的其他排他锁请求,需要等它释放后才能成功。
3.2 在第二个终端尝试执行
趁第一个终端还没有显示 END,在第二个终端执行:
/usr/bin/flock -n -E 75 /opt/flock-demo/task.lock \
/bin/echo SECOND_RUN
printf '退出码:%s\n' "$?"
正常情况下:
- 不会输出
SECOND_RUN。 - 退出码为
75。
这说明第二次调用没有取得锁,因此没有启动后面的任务。
printf 应紧接着执行,用于读取上一条命令的退出状态。
3.3 等待锁释放后再次执行
第一个终端显示 END 后,在第二个终端重新执行同一组命令。
此时应看到:
SECOND_RUN
退出码:0
这说明前一次任务结束后,新的调用可以正常取得锁。
本例不需要手动删除 task.lock。
4. 给已有的Cron任务加锁
假设已有任务脚本为:
/opt/myjob/run.sh
原来的定时任务是每5分钟执行一次:
*/5 * * * * /bin/bash /opt/myjob/run.sh
以下路径均为示例,需要对应到实际项目。其中,项目目录必须已经存在,任务用户需要有创建锁文件和写入日志的权限。
4.1 使用任务原本的用户编辑
先确认用户和当前任务:
whoami
crontab -l
然后执行:
crontab -e
将原来的那一条任务替换为:
*/5 * * * * /usr/bin/flock -n -E 75 --verbose /opt/myjob/task.lock /bin/bash -c 'cd /opt/myjob && exec /bin/bash ./run.sh' >> /opt/myjob/cron.log 2>&1
整条配置写在同一行,替换原记录,避免同时保留两条任务。
这条命令会:
- 尝试取得
/opt/myjob/task.lock的锁。 - 取得锁后,进入项目目录并执行脚本。
- 没有取得锁时,跳过本次执行。
- 将输出和错误追加到
cron.log。
--verbose 可以输出取得锁或未能取得锁的相关信息,便于排查。
4.2 区分用户任务和系统任务格式
上面的示例用于通过 crontab -e 编辑的用户任务。
如果写入 /etc/crontab 或 /etc/cron.d/,时间字段后还需要增加执行用户名。两种格式不能直接混用。
通过 crontab -e 正常保存修改后,Cron 会重新加载配置,通常不需要重启服务。
如果脚本负责定时同步文件,可以结合本站的:
使用时应让锁覆盖完整的同步过程。
5. 上一轮没结束,应该跳过还是等待?
这取决于任务能否接受某一次触发不执行。
| 写法 | 拿不到锁时的行为 |
|---|---|
不加 -n 或 -w | 等待锁可用 |
-n | 立即退出,本轮不执行 |
-w 60 | 最多等待60秒,超时后退出 |
例如,允许跳过某次运行的周期性统计刷新,可以使用 -n。
如果希望短暂等待上一轮结束,可以使用:
/usr/bin/flock -w 60 -E 75 /opt/myjob/task.lock \
/bin/bash -c 'cd /opt/myjob && exec /bin/bash ./run.sh'
这里的60秒是等待取得锁的时间上限,不是脚本运行时间上限。取得锁以后,任务仍可能运行更长时间。
还需要明确:
-n不会终止正在运行的上一轮任务。- 被跳过的这一轮不会自动补跑。
- 默认等待可能让频繁触发的任务积压。
如果每一次业务任务都必须完成,需要配合任务队列、执行记录和重试机制,不能只依靠跳过来处理。

6. 使用文件锁时容易忽略的问题
6.1 锁文件存在,不代表任务仍在运行
task.lock 可以长期保留在磁盘上。
锁与打开的文件描述有关,相关描述全部关闭后,锁会释放。文件本身仍然存在,并不等于它还被锁定。
运行期间不要把删除锁文件当作“解锁”方式。删除后重新创建的同名文件可能是另一个文件对象,反而让后续任务绕过原来的锁。
6.2 所有执行入口要使用同一把锁
如果 Cron 使用了 flock,手动执行时却直接运行脚本,手动启动的任务仍可能与 Cron 任务重叠。
手动执行时,也应通过相同的锁文件调用:
/usr/bin/flock -n -E 75 --verbose /opt/myjob/task.lock \
/bin/bash -c 'cd /opt/myjob && exec /bin/bash ./run.sh'
本地文件锁需要各个入口共同遵守加锁约定,不会自动阻止绕过加锁命令启动的程序。
6.3 业务过程应在锁的保护范围内
脚本应等待实际业务完成后再退出。
如果脚本只是提交了一个后台任务就立即结束,后台进程如何继承或关闭文件描述符,会影响锁的持有时间,不能简单假设“加了flock就一定覆盖全部工作”。
对于本文这种用法,保持任务在前台执行、完成后再退出,更容易验证。
6.4 多台服务器需要另外设计
两台服务器各自创建同名的本地锁文件,不会因此形成同一把锁。
如果任务可能从多台服务器同时启动,需要采用适合业务的统一调度或跨主机协调方案。NFS、CIFS 等网络文件系统上的锁行为,还应结合具体文件系统和挂载配置验证。
7. 加锁后怎么确认生效?
先完成第3节的独立演示,再观察正式任务的正常调度。
查看日志:
tail -n 50 /opt/myjob/cron.log
建议核对:
- 上一轮运行期间,后续调用是否因未取得锁而退出。
- 上一轮结束后,下一次计划执行能否正常开始。
- 实际文件同步、备份或统计结果是否完成。
- 日志中是否存在路径、权限或脚本本身的错误。
如果持续拿不到锁,要检查上一轮是否耗时过长、卡住,或者仍有子进程持有锁。
另外,flock 成功启动任务后,会返回任务自身的退出状态。因此,正式脚本如果也使用退出码75,就需要结合日志判断,不能把所有75都当作锁冲突。
8. 结语
Linux定时任务重复执行时,先检查是否存在重复配置,再确认任务运行时间是否超过调度间隔。
对于单台服务器上需要避免重叠的任务,可以使用 flock,让所有执行入口共用同一个锁文件,并根据业务要求选择立即跳过或限时等待。
加锁以后,还应持续观察执行耗时和业务结果。文件锁可以控制并发,但任务卡住、脚本错误和业务数据重复处理,仍需要分别解决。








