如何避免 bash 脚本中的竞争条件?

Sti*_*y21 5 bash race-condition

#!/bin/bash
if [ ! -f numbers ]; then echo 0 > numbers; fi
count=0
touch numbers
echo $count > numbers
while [[ $count != 100 ]]; do
  if ln numbers numbers.lock
  then
    count=`expr $count + 1`
    n=`tail -1 numbers`
    expr $n + 1 >> numbers
    rm numbers.lock
  fi
done
Run Code Online (Sandbox Code Playgroud)

count=`expr $count + 1`我能做些什么来避免和 的竞争条件n=`tail -1 numbers`,这样当我同时运行两个脚本时,它只会达到 100,而不是 200。我研究了多个网站,但没有简洁的答案没有做一个巨大的功能。

tri*_*eee 7

您已经可以安全地避免使用锁定文件的实际竞争条件。您所描述的问题可以通过两种方式避免。

(1) 将锁定文件移到主循环之外,以便程序的两个实例不能同时运行其主循环。如果其中一个正在运行,另一个将必须等待它完成,然后开始替换输出文件。

#!/bin/bash

# FIXME: broken, see comments

while true; do
    if ! ln numbers numbers.lock
    then
       sleep 1
    else
        if [ ! -f numbers ]; then echo 0 > numbers; fi
        count=0
        touch numbers
        #echo $count > numbers   # needless, isn't it?
        while [[ $count != 100 ]]; do
            count=`expr $count + 1`
            n=`tail -1 numbers`
            expr $n + 1 >> numbers
            rm numbers.lock
        done
        break
    fi
done
Run Code Online (Sandbox Code Playgroud)

(2) 通过检查文件的内容使两个实例合作。换句话说,当数量达到 100 时强制它们停止循环,无论有多少其他进程正在写入此文件。(我猜当运行的实例超过 100 个时,会出现一个不确定的极端情况。)

#!/bin/bash
# FIXME: should properly lock here, too
if [ ! -f numbers ]; then echo 0 > numbers; fi
n=0
touch numbers
while [[ $n -lt 100 ]]; do
  if ln numbers numbers.lock
  then
    n=$(expr $(tail -1 numbers) + 1 | tee numbers)
    rm numbers.lock
  fi
done
Run Code Online (Sandbox Code Playgroud)

根据您的要求,您实际上可能希望脚本在启动脚本的新实例时破坏文件中的任何先前值,但如果不是,则也echo 0 > numbers应该由锁定文件控制。

expr你确实想在 Bash 脚本中避免;Bash 有内置的算术运算符。我没有尝试在这里重构该部分,但您可能应该这样做。也许更喜欢 Awk,这样你也可以将其分解出来tailawk '{ i=$0 } END { print 1+i }' numbers