スクリプトをn回同時に実行する方法とセマフォをシミュレートする方法は?

スクリプトをn回同時に実行する方法とセマフォをシミュレートする方法は?

内部に数字のテキストファイルがあり、kshにscript.shがあります。スクリプトはファイルを読み取り、数字を取得し、数字を1ずつ増やし、ファイル内の新しい数字を上書きし、しばらく待機し、数字が120になるまでプロセスを繰り返します。

このスクリプトを同時にn回実行したいと思います。どうすればいいですか?

ただし、file.txtを編集したいn個のプロセスがあり、1つのプロセスのみを編集するためにプロセスが完了(スリープ)されると、2番目のプロセスが編集できるようになります。

ロックファイルを使用する必要があると言うこともできますが、残念ながら使用できないため、セマフォをシミュレートする別の方法を見つける必要があります。

どんなアイデアがありますか?

ありがとうございます。

答え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

ファイルをロックせずにこれを行うには、何か奇妙なことが必要なようです。ファイルをロックすること(またはディレクトリの作成がアトミックである必要があるため、ディレクトリをロックする方が良い)は、この問題に対する標準的で実行可能なソリューションであるためです。私はあなたが望むものを正確に実行しませんでしたが、心に浮かんだいくつかのアイデアは次のとおりです。

SysVセマフォの状態を確認するために小さなCプログラムを書くことができます。man semgetまたはで始めますman semop。退屈でおかしいでしょう。

OraclesqlplusブロックとPL/SQLブロックを使用して、次の作業を実行できます。

lock table table_name in exclusive mode

その後、もう一度呼び出してsqlplusロックを解除します。以前に同様のことをしたことがありますが、プロセスが待機したりロックを解除したりしないように注意する必要があります。事務職にのみ関心があり、電話を1回だけすることもありますsqlplus

左側のフィールドでは、名前付きパイプをミューテックスとして使用できます。あなたは本当にそれを試す必要があります。

カーネルモジュールをロードできる場合は、カーネルモジュールは/procダミーファイルを使用してミューテックスまたはセマフォとして機能できます。 IBM DeveloperWorksは記事ファイルが生成されるロード可能モジュールから/proc

たぶんあなたは実装することができますデッカーアルゴリズムファイルまたは名前付きパイプの値を使用します。

semop()私が書いたものを見直した後、Cプログラムを除いて、これが実際に動作するかどうかはわかりません。すべて多くの実験が必要です。

答え3

仮定:

  • スクリプトのすべてのインスタンスは同じコンピュータで実行されます。
  • あなたのスクリプトは知られていますが、他のプログラムでは使用されていないディレクトリに書き込むことができます。ディレクトリは「一般」ファイルシステム(特に非NFS)にあります。

set -C; (: >foo) 2>/dev/nullファイル生成()、名前変更()、削除()mvなどのアトミック操作を使用してrmロックを処理できます。

プロセスに通知するには、シグナルを送信する必要があります。しかし、プロセスをターゲットにすることは問題がある。プロセスIDをどこかに保存すると、IDがまだ有効かどうか、関連していないプロセスで再利用されたことを確認できません。 2つのプロセスを同期させる1つの方法は、パイプにバイトを書き込むことです。リーダーは、作成者が表示されるまでブロックされ、その逆も同様です。

まずディレクトリを設定します。名前付きファイル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。ロックは、3番目のインスタンスが起動するまで保持されます。また、スクリプトがtake_lockor内で消えないと仮定しますrelease_lock

警告:上記のコードは私がブラウザで直接書いたものです。まだテストしていません(正確であることの証明ははるかに少ない)。

関連情報