주어진 시간에 하나의 쉘 스크립트 인스턴스 만 실행되도록하는 가장 빠르고 더러운 방법은 무엇입니까?
답변
다음은 잠금 파일 을 사용 하고 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
