如何同时运行脚本 n 次以及如何模拟信号量?

如何同时运行脚本 n 次以及如何模拟信号量?

我有一个文本文件,该文件内部是一个数字,并且我在 ksh 中有一个 script.sh 。该脚本读取文件并获取数字,然后将数字加 1 并覆盖文件中的新数字,然后休眠一段时间并重复该过程,直到数字等于 120。

我想让这个脚本同时运行n次,我该怎么做?

但随后我将有 n 个进程尝试编辑 file.txt,并且为了只有一个进程进行编辑,当它完成(休眠)时,第二个进程可以编辑,依此类推...

也许你会告诉我,我必须使用锁文件,但不幸的是我不能使用它,所以我必须找到另一种方法来模拟信号量。

有任何想法吗?

谢谢。

答案1

我更喜欢从外部控制数字的操作,只需调用脚本并将当前数字作为参数传递给它:

n=10
read nr < number.file
seq $((nr+1)) 120 | xargs -n 1 -P $n script.sh

脚本本身将简化为如下所示:

#!/bin/ksh
number=$1
echo "Do job with/for number $number"
echo $number > number.file
sleep 10

当然,如果任务的持续时间可能会有所不同,那么最好在写入之前检查数字文件的当前内容,以免覆盖更大的文件。但只有当任务的连续性需要支持某种类型的恢复时,这一点才重要。

答案2

在没有锁定文件的情况下执行此操作似乎需要一些奇怪的东西,因为锁定文件(更好的是锁定目录,因为目录创建应该是原子的)是此问题的标准且可行的解决方案。我还没有完全按照你想做的做,但这里有一些我想到的想法:

您可以编写一个小型 C 程序来检查 SysV 信号量的状态。以man semget或开头man semop。这将是乏味且奇怪的。

您可以使用 Oraclesqlplus和 PL/SQL 块来执行以下操作:

lock table table_name in exclusive mode

然后再次调用 来sqlplus释放锁。我以前做过类似的事情,你必须非常小心,不要让进程等待,并释放锁。我也可能只对桌面上的工作感兴趣,所以我只打过一次电话sqlplus

在左侧字段中,您可以使用命名管道作为互斥锁。你真的必须尝试一下。

如果您可以加载内核模块,也许内核模块可以使用/proc虚拟文件来充当互斥体或信号量。 IBM DeveloperWorks 有一篇文章在创建文件的可加载模块上/proc

也许你可以实施德克尔算法使用文件或命名管道中的值。

在回顾了我写的内容之后,我不太确定除了 C 程序之外的任何这些semop()是否真的有效。它们都需要大量的实验。

答案3

假设:

  • 您的所有脚本实例都在同一台计算机上运行。
  • 您的脚本可以在一个已知的目录中写入,并且其他程序不会使用该目录。该目录位于“普通”文件系统(特别是非 NFS)上。

set -C; (: >foo) 2>/dev/null您可以使用文件创建( )、重命名(mv)和删除( )等原子操作rm来处理锁定。

要通知进程,您可以向其发送信号;然而,定位目标进程是有问题的:如果将进程 ID 存储在某处,则无法确定这些 ID 是否仍然有效,它们可能已被不相关的进程重用。同步两个进程的一种方法是在管道上写入一个字节;读者将阻塞直到作家出现,反之亦然。

首先,设置目录。创建一个名为 的文件lock和一个名为 的命名管道pipe

if ! [ -d /script-locking-directory ]; then
  # The directory doesn't exist, create and populate it
  {
    mkdir /script-locking-directory-$$ &&
    mkfifo /script-locking-directory-$$/pipe &&
    touch /script-locking-directory-$$/lock &&
    mv /script-locking-directory-$$ /script-locking-directory
  } || {
    # An error happened, so clean up
    err=$?
    rm -r /script-locking-directory-$$
    # Exit, unless another instance of the script created the directory
    # at the same time as us
    if ! [ -d /script-locking-directory ]; then exit $?; fi
  }
fi

我们将通过重命名文件来实现锁定lock。此外,我们将使用一个简单的方案来通知锁上的所有等待者:将一个字节回显到管道,并让所有等待者通过从该管道读取数据来等待。这是一个简单的方案。

take_lock () {
  while ! mv lock lock.held 2>/dev/null; do
    read <pipe # wait for a write on the pipe
  done
}
release_lock () {
  mv lock.held lock
  read <pipe & # make sure there is a reader on the pipe so we don't block
  echo >pipe # notify all readers
}

该方案唤醒所有服务员,这可能效率低下,但是除非存在大量争用(即同时有很多服务员),否则这不会成为问题。

上面代码的主要问题是,如果锁持有者死亡,锁将不会被释放。我们怎样才能发现这种情况呢?由于PID重用,我们不能只去寻找像锁这样的进程。我们可以做的是打开脚本中的锁定文件,并在新的脚本实例启动时检查锁定文件是否打开。

break_lock () {
  if ! [ -e "lock.held" ]; then return 1; fi
  if [ -n "$(fuser lock.held)" ]; then return 1; fi
  # If we get this far, the lock holder died
  if mv lock.held lock.breaking.$$ 2>/dev/null; then
    # Check that someone else didn't break the lock and take it just now
    if [ -n "$(fuser lock.breaking.$$)" ]; then
      mv lock.breaking.$$ lock.held
      return 0
    fi
    mv lock.breaking.$$ lock
  fi
  return 0 # whether we did break a lock or not, try taking it again
}
take_lock () {
  while ! mv lock lock.taking.$$ 2>/dev/null; do
    if break_lock; then continue; fi
    read <pipe # wait for a write on the pipe
  done
  exec 9<lock.taking.$$
  mv lock.taking.$$ lock.held
}
release_lock () {
  # lock.held might not exist if someone else is trying to break our lock.
  # So we try in a loop.
  while ! mv lock.held lock.releasing.$$ 2>/dev/null; do :; done
  exec 9<&-
  mv lock.releasing.$$ lock
  read <pipe & # make sure there is a reader on the pipe so we don't block
  echo >pipe # notify all readers
}

如果锁持有者在另一个实例处于内部时死亡,则此实现仍然可能死锁take_lock:锁将保持持有状态,直到第三个实例启动。我还假设脚本不会在take_lock或内消失release_lock

警告:上面的代码是我直接在浏览器中编写的,我没有测试它(更不用说证明它是正确的)。

相关内容