我一直在尝试改进一些 shell 代码,以实现破坏的锁定机制。

我的想法是通过在文件上调用rm来仅允许一个调用者完成同步。

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

echo $$ > $PIDFILE
# this should succeed only for one process
rm $flag || exit
echo $$ > $PIDFILE

我已经进行了一些并发 call ,并为此投入了很多精力,但并没有遇到失败。

但这实际上是安全的吗?

最佳答案

不安全

假设脚本的三个副本(A,B和C)同时启动,并且/tmp/test.pid最初不存在。

让A和B完成脚本的初始语句:

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

切换到A并让它运行另外两个语句:
echo $$ > $PIDFILE
rm $flag || exit

这成功了; $PIDFILE现在包含A的PID。

切换到B并让其运行相同的语句。 rm失败,因此B退出,但是$PIDFILE现在包含B的PID。

切换到C。C才刚刚开始运行,因此它要做的第一件事是重新创建$flag:
PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

现在进行PID检查:
if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

这是因为$PIDFILE包含B的PID,但是B不再运行。

现在我们去
echo $$ > $PIDFILE
rm $flag || exit

这也通过了,因为C刚刚重新创建了$flag文件。

现在我们同时运行A和C,相互竞争以再次覆盖$PIDFILE

除此之外,还有一个“误报”问题:
if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

您可能有一个过时的$PIDFILE,但是其中包含的PID已被其他进程重用。在这种情况下,您不会遇到竞争(脚本实例过多),而会拒绝服务(脚本实例过多:0)。您的脚本将看到正在运行的过程,恰好恰好具有“错误的” PID并退出。

10-04 20:34