簡要
在持續集成(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就直接退出了, 不在執行