简要
在持续集成(CI)中, 我们的项目使用的是 shell, 某个stages是需要单例执行(因为要独占进程). 因此想到了要使用单例. 等待执行.
第一种方案
代码
#!/bin/bash
file_name=`basename $0`
echo $file_name
while [ `pgrep -f ${file_name} | wc -l` -gt 2 ]; do
echo ${file_name} process existed, sleep 5s
sleep 5
done
echo 开始执行
sleep 8
echo 执行结束
解释
bashename $0 只取文件名
例如 bash ~/demo/test.sh $0=~/demo/test.sh bashename $0=test.sh
pgrep -f ${file_name}输出含有file_name的pid
wc -l 计数
理论上 大于1即可, 但是为什么要大于2呢, 本身占一个进程(bash ./test.sh), 内部又有执行外部命令pgrep -f ${file_name} | wc -l 占一个进程, 共两个进程, 如果在执行一次bash ./test.sh必定大于2了.
该方法理论上看上去没什么问题, 但是当堆积起来时(比如堆积3个时), 必定都陷入了等待. 两个是没有问题的看图.
稍微改了下代码, 便于查看 count数
#!/bin/bash
file_name=`basename $0`
echo $file_name
count=`pgrep -f ${file_name} | wc -l`
while [ $count -gt 2 ]; do
echo ${file_name} process existed, sleep 5s count=$count
sleep 5
count=`pgrep -f ${file_name} | wc -l`
done
echo 开始执行
sleep 8
echo 执行结束
结果显示
只有两个的情况下

三个情况, 就会死循环了…

因此这种方法被舍弃, 主要还有一点就是pgrep -f ${file_name}输出含有file_name的pid, 注意是含有, 当有执行例如vim test.sh时候, 也会记录count
第二种方案
使用flock, 先看看 flock如何使用
$ flock -h
Usage:
flock [options] <file>|<directory> <command> [<argument>...]
flock [options] <file>|<directory> -c <command>
flock [options] <file descriptor number>
Manage file locks from shell scripts.
选项:
-s, --shared get a shared lock
-x, --exclusive get an exclusive lock (default)
-u, --unlock remove a lock
-n, --nonblock fail rather than wait
-w, --timeout <secs> wait for a limited amount of time
-E, --conflict-exit-code <number> exit code after conflict or timeout
-o, --close close file descriptor before running command
-c, --command <command> run a single command string through the shell
--verbose increase verbosity
-h, --help display this help and exit
-V, --version output version information and exit
For more details see flock(1).
-s为共享锁,在定向为某文件的FD上设置共享锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置独占锁的请求失败,而其他进程试图在定向为此文件的FD上设置共享锁的请求会成功。
-e为独占或排他锁,在定向为某文件的FD上设置独占锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置共享锁或独占锁都会失败。只要未设置-s参数,此参数默认被设置。
-u手动解锁,一般情况不必须,当FD关闭时,系统会自动解锁,此参数用于脚本命令一部分需要异步执行,一部分可以同步执行的情况。
-n为非阻塞模式,当试图设置锁失败,采用非阻塞模式,直接返回1,并继续执行下面语句。
-w设置阻塞超时,当超过设置的秒数,就跳出阻塞,返回值设置为1,并继续执行下面语句。
-o必须是使用第一种格式时才可用,表示当执行command前关闭设置锁的FD,以使command的子进程不保持锁。
-c执行其后的comand。
最终使用 flock -n
代码
#!/bin/bash
file_name=`basename $0`
echo $file_name
readonly LOCKFILE="~/$file_name.pid"
readonly FD=$(ls -l /proc/$$/fd | sed -n '$p' | awk '{print $9}')
readonly LOCKFD=$(( ${FD}+1 ))
eval "exec ${LOCKFD}>${LOCKFILE}"
flag=0
while [ $flag -eq 0 ]; do
flag=1
flock -n ${LOCKFD} && echo $$>&${LOCKFD} || flag=0
echo $flag
sleep 1
done
echo "开始执行"
sleep 5
echo "z执行结束"
解释
readonly表示变量只读
LOCKFILE 正如 在使用apt-get的时候发生错误会说 xxx.pid被占用, 跟那个同样的道理
FD文件描述符
eval "exec ${LOCKFD}>${LOCKFILE}" 打开文件LOCKFILE并把它关联到文件描述符LOCKFD
while之后就一个循环, 获得到锁, 就不会执行 || flag=0 就会退出循环
flock -n ${LOCKFD}尝试获得锁, 得到锁, 会把自己的pid$$写入到文件中.
结果

最后
可能有人会说, 怎么不需要释放锁, 因为, 程序结束, 会自动释放释放文件描述符, 也就自动释放了锁, 如果程序中还有别的操作, 建议手动释放.
可能还有人会说, 我想让它执行一次就行了, 不需要每个脚本都执行.那么把代码改成这样就行了, ||flag=0改成’exit 1`即可
#!/bin/bash
file_name=`basename $0`
echo $file_name
readonly LOCKFILE="~/$file_name.pid"
readonly FD=$(ls -l /proc/$$/fd | sed -n '$p' | awk '{print $9}')
readonly LOCKFD=$(( ${FD}+1 ))
eval "exec ${LOCKFD}>${LOCKFILE}"
flock -n ${LOCKFD} && echo $$>&${LOCKFD} || exit 1
echo "开始执行"
sleep 5
echo "z执行结束"
其他shell就直接退出了, 不在执行

在CI中,为了实现shell脚本的单例执行,文章探讨了两种方案。第一种通过检查pid文件数量,但存在等待堆积的问题;第二种方案利用flock进行文件锁定,确保脚本在已运行状态下不会重复执行,且在程序结束时自动释放锁。

1万+

被折叠的 条评论
为什么被折叠?



