[bash] 한 번에 하나의 셸 스크립트 인스턴스 만 실행되도록하는 빠르고 더러운 방법

주어진 시간에 하나의 쉘 스크립트 인스턴스 만 실행되도록하는 가장 빠르고 더러운 방법은 무엇입니까?



답변

다음은 잠금 파일 을 사용 하고 PID를 에코 하는 구현입니다 . 이것은 pidfile을 제거하기 전에 프로세스가 종료 된 경우 보호 기능을합니다 .

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "already running"
    exit
fi

# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

# do stuff
sleep 1000

rm -f ${LOCKFILE}

여기서 트릭은 kill -0신호를 전달하지 않지만 주어진 PID를 가진 프로세스가 존재하는지 확인하는 것입니다. 또한 호출하는 trap수 있도록합니다 잠금 파일은 프로세스가 (제외 사망하는 경우도 제거됩니다 kill -9).


답변

사용은 flock(1)파일 기술자에 대한 독점적 인 범위의 잠금 A를 확인합니다. 이 방법으로 스크립트의 다른 부분을 동기화 할 수도 있습니다.

#!/bin/bash

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

코드 사이의이 보장하지만 (하고 )한 번에 하나 개의 프로세스 만이 실행되는 프로세스가 너무 오래 잠금을 대기하지 않습니다.

주의 사항 :이 특정 명령은의 일부입니다 util-linux. Linux 이외의 운영 체제를 실행하는 경우 운영 체제가 사용 가능하지 않을 수 있습니다.


답변

“파일 잠금”의 존재를 테스트하는 모든 접근 방식에는 결함이 있습니다.

왜? 파일이 존재하는지 확인하고 단일 원자 조치로 파일을 작성할 수있는 방법이 없기 때문입니다. 이것 때문에; 경쟁 조건이 것입니다 상호 배제 휴식에 당신의 시도를이.

대신을 사용해야 mkdir합니다. mkdir디렉토리가 없으면 디렉토리를 작성하고 존재하면 종료 코드를 설정합니다. 더 중요한 것은 단일 원자 동작 으로이 모든 것을 수행 하여이 시나리오에 완벽하게 만듭니다.

if ! mkdir /tmp/myscript.lock 2>/dev/null; then
    echo "Myscript is already running." >&2
    exit 1
fi

자세한 내용은 우수한 BashFAQ를 참조하십시오. http://mywiki.wooledge.org/BashFAQ/045

오래된 잠금 장치를 관리하려면 퓨저 (1) 가 유용합니다. 여기서 유일한 단점은 작업이 약 1 초가 걸리므로 즉각적이지 않다는 것입니다.

다음은 퓨저를 사용하여 문제를 해결하는 함수입니다.

#       mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
    local file=$1 pid pids

    exec 9>>"$file"
    { pids=$(fuser -f "$file"); } 2>&- 9>&-
    for pid in $pids; do
        [[ $pid = $$ ]] && continue

        exec 9>&-
        return 1 # Locked by a pid.
    done
}

다음과 같이 스크립트에서 사용할 수 있습니다.

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

이식성에 신경 쓰지 않는다면 (이 솔루션은 거의 모든 UNIX 상자에서 작동해야합니다) Linux의 fuser (1) 은 몇 가지 추가 옵션을 제공하며 flock (1)도 있습니다.


답변

flock (2) 시스템 호출에는 flock (1)이라고하는 래퍼가 있습니다. 이렇게하면 정리 등을 걱정하지 않고 독점 잠금을 비교적 쉽게 얻을 수 있습니다 . 셸 스크립트에서이를 사용하는 방법 에 대한 예제 가 맨 페이지 에 있습니다.


답변

무리와 같은 원자 연산이 필요합니다. 그렇지 않으면 결국 실패합니다.

그러나 무리를 사용할 수 없으면 어떻게해야합니까? mkdir이 있습니다. 그것은 원자 작업입니다. 하나의 프로세스 만 mkdir을 성공적으로 수행하고 다른 프로세스는 모두 실패합니다.

코드는 다음과 같습니다.

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

스크립트가 다시 실행되지 않는 충돌이 발생하면 오래된 잠금을 처리해야합니다.


답변

잠금을 안정적으로 만들려면 원 자성 작업이 필요합니다. 위의 제안 중 많은 부분이 원자가 아닙니다. 제안 된 lockfile (1) 유틸리티는 언급 된 맨 페이지에서 “NFS- 내성”이라는 유망한 것으로 보입니다. OS가 lockfile (1)을 지원하지 않고 솔루션이 NFS에서 작동해야하는 경우 옵션이 많지 않습니다 …

NFSv2에는 두 가지 원자 연산이 있습니다.

  • 심볼릭 링크
  • 이름을 바꾸다

NFSv3을 사용하면 create 호출도 원 자성입니다.

디렉토리 작업은 NFSv2 및 NFSv3에서 원자 적이 지 않습니다 (Brent Callaghan의 ISBN ‘NFS Illustrated'(ISBN 0-201-32570-5, Brent는 Sun의 NFS 베테랑) 참조).

이것을 알면 파일과 디렉토리 (PHP가 아닌 셸)에 대한 스핀 잠금을 구현할 수 있습니다.

현재 디렉토리 잠금 :

while ! ln -s . lock; do :; done

파일을 잠그십시오.

while ! ln -s ${f} ${f}.lock; do :; done

현재 디렉토리 잠금 해제 (가정, 실행중인 프로세스가 실제로 잠금을 획득 함) :

mv lock deleteme && rm deleteme

파일 잠금 해제 (가정, 실행중인 프로세스가 실제로 잠금을 획득 함) :

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

제거도 원자가 아니므로 먼저 원자 이름을 변경 한 다음 제거하십시오.

심볼릭 링크 및 이름 바꾸기 호출의 경우 두 파일 이름이 모두 동일한 파일 시스템에 있어야합니다. 내 제안 : 간단한 파일 이름 (경로 없음) 만 사용하고 파일을 넣고 동일한 디렉토리에 고정하십시오.


답변

또 다른 옵션은을 noclobber실행 하여 쉘 옵션을 사용 하는 것 set -C입니다. 그때> 파일이 이미 존재하는 경우 실패합니다.

간단히 :

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

쉘이 다음을 호출하게합니다.

open(pathname, O_CREAT|O_EXCL)

원자 적으로 파일을 작성하거나 파일이 이미 존재하는 경우 실패합니다.


BashFAQ 045 에 대한 의견에 따르면 이것은 실패 할 수 ksh88있지만 모든 쉘에서 작동합니다.

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

플래그 를 pdksh추가하는 O_TRUNC것이 흥미롭지 만 분명히 중복됩니다.
빈 파일을 만들거나 아무것도하지 않습니다.


rm부정 행위 를 처리 할 방법에 따라 방법이 달라집니다.

정리 종료시 삭제

부정확 한 종료가 발생한 문제가 해결되고 잠금 파일이 수동으로 제거 될 때까지 새 실행이 실패합니다.

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

모든 출구에서 삭제

스크립트가 아직 실행되고 있지 않으면 새로운 실행이 성공합니다.

trap 'rm "$lockfile"' EXIT