스크립트를 n번 동시에 실행하는 방법과 세마포어를 시뮬레이션하는 방법은 무엇입니까?

스크립트를 n번 동시에 실행하는 방법과 세마포어를 시뮬레이션하는 방법은 무엇입니까?

내부에 숫자가 있는 텍스트 파일이 있고 ksh에 script.sh가 있습니다. 스크립트는 파일을 읽고 숫자를 가져온 다음 숫자를 1씩 증가시키고 파일의 새 숫자를 덮어쓴 다음 잠시 동안 대기하고 숫자가 120이 될 때까지 프로세스를 반복합니다.

이 스크립트를 동시에 n번 실행하고 싶습니다. 어떻게 해야 합니까?

하지만 file.txt를 편집하려는 n개의 프로세스가 있을 것이고, 하나의 프로세스만 편집하기 위해 프로세스가 완료(휴면)되면 두 번째 프로세스가 편집할 수 있게 됩니다.

잠금 파일을 사용해야 한다고 말씀하실 수도 있겠지만, 아쉽게도 사용할 수 없어서 세마포어를 시뮬레이션할 수 있는 다른 방법을 찾아야 합니다.

어떤 아이디어가 있나요?

감사해요.

답변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. 지루하고 이상할 겁니다.

Oracle sqlplus및 PL/SQL 블록을 사용하여 다음 작업을 수행할 수 있습니다.

lock table table_name in exclusive mode

그런 다음 다시 호출하여 sqlplus잠금을 해제합니다. 이전에 비슷한 일을 한 적이 있는데 프로세스가 기다리거나 잠금이 해제되지 않도록 매우 주의해야 합니다. 사무직에만 관심이 있어서 전화 한 번만 하는 경우도 있을 수 있습니다 sqlplus.

왼쪽 필드에서는 명명된 파이프를 뮤텍스로 사용할 수 있습니다. 당신은 정말로 그것을 시도해야합니다.

커널 모듈을 로드할 수 있다면 커널 모듈은 /proc더미 파일을 사용하여 뮤텍스나 세마포어 역할을 할 수 있습니다. IBM DeveloperWorks는기사파일이 생성되는 로드 가능한 모듈에서 /proc.

어쩌면 당신은 구현할 수 있습니다데커 알고리즘파일이나 명명된 파이프의 값을 사용합니다.

semop()내가 작성한 내용을 검토한 후에 C 프로그램을 제외하고는 이것이 실제로 작동하는지 확신할 수 없습니다 . 모두 많은 실험이 필요합니다.

답변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_lockor 내에서 사라지지 않는다고 가정합니다 release_lock.

경고: 위의 코드는 제가 브라우저에서 직접 작성한 것입니다. 아직 테스트하지 않았습니다(정확하다는 입증은 훨씬 적습니다).

관련 정보