在 Linux Shell 中如何只允许同时最多一个程序运行

2022-04-03

在 Linux shell 中有时需要实现排他地同时最多只能一个进程运行,比如用 crontab 周期性执行程序,定时开始时可能之前周期运行的程序还没有结束退出,此时就需要用到下面几种方案。

flock 命令

flock -xn <lock-file> <script>

Linux 里的文件锁主要两种,一种是协同锁(advisory lock),一种是强制锁(mandatory lock),协同锁不是由操作系统或者文件系统设置,它要求参与操作的进程之间协同工作, 文件被协同锁锁定时也一样可以被系统调用去读写甚至删除,强制锁通过命令 fcntl 操作,linux 的强制锁使用有一定限制,而且 kernel 文件中是建议尽量不用强制锁。

flock 会给文件上协同锁,不同的进程可以通过 flock 命令协同工作。-x 参数是排他锁,这个是默认配置。如果已有进程给文件上锁,新启动的 flock 进程默认会一直等拿到锁再执行命令, -n 是 nonblock,拿不到锁就立刻退出,exit code 默认是 1,可以通过参数 -E 指定其他 exit code。 -w 可以指定等待几秒后拿不到锁再退出。-s 是指定为共享锁,读锁。

下面两个命令可以查看 linux 系统锁。

lslocks
# COMMAND PID TYPE SIZE MODE M START END PATH
# flock 19619 FLOCK 4B WRITE 0 0 0 /home/tony/test/balance.dat
cat /proc/locks
# 1: FLOCK ADVISORY WRITE 19619 08:10:83966 0 EOF

使用 PID 文件

PID 文件就是普通的文本文件,只保存进程的 PID,这里面没有特别规则,只是一种约定。 使用 PID 文件的想法就是在进程开始前检查是否存在 PID 文件,及存储的 pid 进程是否有效,如果都是 True 则等待,否则开始启动本次 action, 结束后移除 PID 文件,防止程序意外退出加上 trap 命令捕捉 EXIT 信号只要退出就移除 PID 文件,但是进程被 kill -9 是无法被捕捉到,所以在检查 PID 文件的时候也要检查下该 pid 程序是否还有效。

trap 命令允许捕获指定信号并在它们发生时执行代码。信号是发送到脚本的异步通知,signal(7) 页面有关于所有信号的介绍, 这篇 Termination-Signals 有关于主要几个中断信号的介绍, kill 命令默认发送 SIGTERM 信号,kill -9 发送 SIGKILL 信号,CTRL-C 操作发送 SIGINT 信号,SIGTERM 信号可以被阻塞,处理和无视,是一种温和的中止信号, 而在 Linux 系统里进程如果收到 SIGKILL 信号必须马上中止,它不能被捕获和无视,自然也就无法被 trap 命令捕获。Turnoff 上有一个有趣的漫画解释 the real reason to not use sigkill

trap 最常用的是捕捉名为 EXIT 的伪信号,可以在脚本退出时执行指定的命令,通常是一些收尾工作,删除临时文件等。trap 只对该命令之后的代码起作用,所以一般把 trap 命令放到文件开始处,trap 在捕获到相应信号,执行指定的命令后会继续执行中断命令后面的脚本,但是不会重新启动被中断的命令。

下面是使用 PID 文件的一个示例。

#!/bin/bash
PIDFILE="/tmp/pidtest.pid"
create_pidfile () {
echo $$ > "$PIDFILE"
}
remove_pidfile () {
[ -f "$PIDFILE" ] && rm "$PIDFILE"
}
# 若存在 PID 文件且 pid 有效则 true,否则 false
check_pidfile () {
# 申明函数局部变量
local prevpid
if [ -f "$PIDFILE" ]; then
prevpid=$(cat "$PIDFILE")
# 参考 man 2 kill
# kill -0 不会发送信号,只会检查是否可执行,可以用作检查进程是否还存在
# 进程存在,且有权限则返回 0,否则返回 1
# 在 shell 中 1 表示 false,0 是 true
kill -0 $prevpid 2>/dev/null
else
false
fi
}
do_action () {
echo "do action..."
sleep 300
}
trap remove_pidfile EXIT
if ! check_pidfile ;then
create_pidfile
do_action
fi

参考

Ensure Only One Instance of a Bash Script Is Running

Introduction to File Locking in Linux

What is a .pid File?

flock(1) - Linux man page

The Bash Trap Command