Oto implementacja, która korzysta z pliku blokującego i wprowadza do niego echo PID. Służy to jako ochrona, jeśli proces zostanie zabity przed usunięciem pliku pid :
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}
Sztuczka polega na kill -0tym, że nie dostarcza żadnego sygnału, a jedynie sprawdza, czy istnieje proces o danym PID. Również wywołanie trapzapewni, że plik blokujący zostanie usunięty, nawet jeśli proces zostanie zabity (z wyjątkiem kill -9).
Jak już wspomniano w komentarzu do innej odpowiedzi, ma to fatalną wadę - jeśli inny skrypt uruchamia się między czekiem a echem, toast.
Paul Tomblin,
1
Sztuczka z dowiązaniem symbolicznym jest fajna, ale jeśli właściciel pliku blokującego zabije -9'd lub system ulegnie awarii, nadal istnieje warunek wyścigu, aby odczytać dowiązanie symboliczne, zauważyć, że właściciela nie ma, a następnie usunąć. Trzymam się mojego rozwiązania.
bmdhacks
9
Kontrola atomowa i tworzenie jest dostępne w powłoce przy użyciu flocka (1) lub pliku blokującego (1). Zobacz inne odpowiedzi.
dmckee --- były moderator kociąt
3
Zobacz moją odpowiedź na przenośny sposób przeprowadzania kontroli atomowej i tworzenia bez konieczności polegania na narzędziach takich jak flock lub plik blokujący.
lhunath,
2
To nie jest atomowe i dlatego jest bezużyteczne. Potrzebujesz mechanizmu atomowego do testowania i ustawiania.
K Richard Pixley,
214
Użyj, flock(1)aby zablokować wyłączny zakres deskryptora pliku. W ten sposób możesz nawet synchronizować różne części skryptu.
#!/bin/bash(# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10200|| exit 1# Do stuff)200>/var/lock/.myscript.exclusivelock
Zapewnia to, że kod pomiędzy (i )jest uruchamiany tylko przez jeden proces na raz i że proces nie czeka zbyt długo na blokadę.
Zastrzeżenie: to konkretne polecenie jest częścią util-linux. Jeśli używasz systemu operacyjnego innego niż Linux, może on być dostępny lub może nie być dostępny.
Co to jest 200? W manulu jest napisane „fd”, ale nie wiem co to znaczy.
chovy
4
@chovy „deskryptor pliku”, uchwyt liczby całkowitej oznaczający otwarty plik.
Alex B
6
Jeśli ktoś zastanawia się: Składnia ( command A ) command Bwywołuje podpowłokę command A. Dokumentowane na tldp.org/LDP/abs/html/subshells.html . Nadal nie jestem pewien terminu wywołania podpowłoki i polecenia B.
Dr Jan-Philip Gehrcke
1
Uważam, że kod wewnątrz podpowłoki powinien być bardziej podobny: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fitak, aby w przypadku przekroczenia limitu czasu (jakiś inny proces zablokował plik) skrypt ten nie działał i nie modyfikował pliku. Prawdopodobnie ... kontrargumentem jest „ale jeśli zajęło to 10 sekund, a blokada nadal nie jest dostępna, nigdy nie będzie dostępna”, prawdopodobnie dlatego, że proces trzymający blokadę nie kończy się (być może jest uruchamiany pod debuggerem?).
Jonathan Leffler,
1
Przekierowany plik jest tylko miejscem docelowym, w którym zamek może działać, nie ma w nim istotnych danych. exitJest od wewnętrznej części (). Po zakończeniu podprocesu blokada jest automatycznie zwalniana, ponieważ nie wstrzymuje go żaden proces.
clacke
158
Wszystkie podejścia testujące istnienie „plików blokujących” są wadliwe.
Czemu? Ponieważ nie ma sposobu, aby sprawdzić, czy plik istnieje i utworzyć go w pojedynczej akcji atomowej. Z tego powodu; istnieje warunek wyścigu, który spowoduje, że twoje próby wzajemnego wykluczenia zostaną przerwane.
Zamiast tego musisz użyć mkdir. mkdirtworzy katalog, jeśli jeszcze nie istnieje, a jeśli tak, ustawia kod wyjścia. Co ważniejsze, robi to wszystko w jednej akcji atomowej, dzięki czemu idealnie nadaje się do tego scenariusza.
Jeśli chcesz zająć się przestarzałymi zamkami, przydaje się utrwalacz (1) . Jedynym minusem jest to, że operacja trwa około sekundy, więc nie jest natychmiastowa.
Oto funkcja, którą napisałem kiedyś, która rozwiązuje problem z użyciem utrwalacza:
# 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>&-return1# Locked by a pid.done}
Jeśli nie zależy ci na przenośności (te rozwiązania powinny działać prawie na każdym systemie UNIX), fuser Linuksa (1) oferuje kilka dodatkowych opcji, a także flock (1) .
Możesz połączyć tę if ! mkdirczęść ze sprawdzeniem, czy proces z PID zapisanym (przy pomyślnym uruchomieniu) wewnątrz lockdir faktycznie działa i jest identyczny ze skryptem do ochrony stalenes. Chroniłoby to również przed ponownym użyciem PID po ponownym uruchomieniu, a nawet nie wymagałoby fuser.
Tobias Kienzler
4
Z pewnością jest prawdą, że mkdirnie jest zdefiniowana jako operacja atomowa i jako taka „efekt uboczny” jest szczegółową implementacją systemu plików. W pełni mu wierzę, jeśli mówi, że NFS nie wdraża go w sposób atomowy. Chociaż nie podejrzewam, że /tmpbędziesz częścią NFS i zapewne dostarczy go fs, który implementuje mkdiratomowo.
lhunath,
5
Istnieje jednak sposób, aby sprawdzić, czy istnieje zwykły plik i utworzyć go atomowo, jeśli nie: za pomocą lnutworzyć twarde łącze z innego pliku. Jeśli masz dziwne systemy plików, które tego nie gwarantują, możesz później sprawdzić i-węzeł nowego pliku, aby sprawdzić, czy jest on taki sam jak oryginalny plik.
Juan Cespedes
4
Jest to „sposób sprawdzić, czy plik istnieje i utworzyć go w jednym działaniu atomowej” - to open(... O_CREAT|O_EXCL). Potrzebujesz do tego odpowiedniego programu użytkownika, takiego jak lockfile-create(in lockfile-progs) lub dotlockfile(in liblockfile-bin). I upewnij się, że odpowiednio wyczyściłeś (np. trap EXIT) Lub przetestowałeś nieaktualne zamki (np. Z --use-pid).
Toby Speight
5
„Wszystkie podejścia, które sprawdzają istnienie„ plików blokujących ”są wadliwe. Dlaczego? Ponieważ nie ma sposobu, aby sprawdzić, czy plik istnieje i utworzyć go w pojedynczej akcji atomowej.” - Aby uzyskać atomowość, należy to zrobić w poziom jądra - i odbywa się to na poziomie jądra przy pomocy flock (1) linux.die.net/man/1/flock, który pojawia się od daty praw autorskich człowieka co najmniej od 2006 roku. Więc napisałem recenzję (- 1), nic osobistego, po prostu mocno przekonajcie, że korzystanie z narzędzi jądra zaimplementowanych przez programistów jądra jest prawidłowe.
Craig Hicks
42
Wokół wywołania systemowego flock (2) znajduje się opakowanie nazywane, niewyobrażalnie, flock (1). To sprawia, że względnie łatwo jest uzyskać wyłączne blokady bez obawy o oczyszczenie itp. Na stronie podręcznika znajdują się przykłady użycia go w skrypcie powłoki.
flock()Wywołanie systemowe nie jest POSIX i nie działa dla plików na NFS.
maxschlepzig
17
Korzystając flock -x -n %lock file% -c "%command%"z zadania Crona, używam do upewnienia się, że tylko jedna instancja jest wykonywana.
Ryall,
Aww, zamiast niewyobrażalnego stada (1), powinni byli wybrać coś takiego jak stado (U). ... ma trochę znajomości. . . wydaje się, że słyszałem to wcześniej.
Kent Kruckeberg,
Warto zauważyć, że dokumentacja flock (2) określa użycie tylko z plikami, ale dokumentacja flock (1) określa użycie z plikiem lub katalogiem. Dokumentacja flock (1) nie mówi wprost o tym, jak wskazać różnicę podczas tworzenia, ale zakładam, że jest to zrobione przez dodanie końcowego „/”. W każdym razie, jeśli flock (1) może obsługiwać katalogi, ale flock (2) nie, wówczas flock (1) nie jest implementowany tylko w przypadku flock (2).
Craig Hicks
27
Potrzebujesz operacji atomowej, takiej jak stado, inaczej to się ostatecznie nie powiedzie.
Ale co zrobić, jeśli stado nie jest dostępne. Cóż, jest mkdir. To także operacja atomowa. Tylko jeden proces zakończy się powodzeniem mkdir, wszystkie inne zakończą się niepowodzeniem.
Więc kod to:
if mkdir /var/lock/.myscript.exclusivelock
then# do stuff:
rmdir /var/lock/.myscript.exclusivelock
fi
Musisz zająć się przestarzałymi blokadami po awarii, skrypt nigdy nie uruchomi się ponownie.
Uruchom to kilka razy jednocześnie (np. „./A.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ”), a skrypt wycieknie kilka razy.
Nippysaurus,
7
@Nippysaurus: Ta metoda blokowania nie przecieka. To, co zobaczyłeś, to początkowy skrypt kończący się przed uruchomieniem wszystkich kopii, więc kolejny mógł (poprawnie) uzyskać blokadę. Aby uniknąć tego fałszywego pozytywu, dodaj sleep 10wcześniej rmdiri spróbuj ponownie kaskadowo - nic nie „wycieknie”.
Sir Athos,
Inne źródła twierdzą, że mkdir nie jest atomowy w niektórych systemach plików, takich jak NFS. A przy okazji widziałem sytuacje, w których na NFS współbieżne rekurencyjne mkdir czasami prowadzi do błędów przy zadaniach matrycy Jenkinsa. Więc jestem całkiem pewien, że tak jest. Ale mkdir jest całkiem niezły dla mniej wymagających przypadków użycia IMO.
akostadinov
Możesz użyć opcji noclobber Bash'a ze zwykłymi plikami.
Palec
26
Aby blokowanie było niezawodne, potrzebujesz operacji atomowej. Wiele z powyższych propozycji nie ma charakteru atomowego. Proponowane narzędzie lockfile (1) wygląda obiecująco, jak wspomniała strona podręcznika, że jest „odporne na NFS”. Jeśli twój system operacyjny nie obsługuje pliku blokującego (1), a twoje rozwiązanie musi działać w systemie plików NFS, masz niewiele opcji ...
NFSv2 ma dwie operacje atomowe:
dowiązanie symboliczne
Przemianować
W NFSv3 wywołanie create jest również atomowe.
Operacje na katalogach NIE są atomowe pod NFSv2 i NFSv3 (patrz książka „NFS Illustrated” Brenta Callaghana, ISBN 0-201-32570-5; Brent jest weteranem NFS w Sun).
Wiedząc o tym, możesz zaimplementować blokady spinów dla plików i katalogów (w powłoce, a nie PHP):
zablokuj bieżący katalog:
while! ln -s . lock;do:;done
zablokuj plik:
while! ln -s ${f} ${f}.lock;do:;done
odblokuj bieżący katalog (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
mv lock deleteme && rm deleteme
odblokuj plik (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Usuń również nie ma charakteru atomowego, dlatego najpierw należy zmienić nazwę (która jest atomowa), a następnie usunąć.
W przypadku wywołań dowiązania symbolicznego i zmiany nazwy oba nazwy plików muszą znajdować się w tym samym systemie plików. Moja propozycja: użyj tylko prostych nazw plików (bez ścieżek) i umieść plik i zablokuj w tym samym katalogu.
Które strony NFS Illustrated obsługują stwierdzenie, że mkdir nie jest atomowy w stosunku do NFS?
maxschlepzig
Dziękujemy za tę technikę. Implementacja muteksu powłoki jest dostępna w mojej nowej bibliotece lib: github.com/Offirmo/offirmo-shell-lib , patrz „mutex”. Używa, lockfilejeśli jest dostępna, lub powrotu do tej symlinkmetody, jeśli nie.
Offirmo,
Miły. Niestety ta metoda nie zapewnia sposobu automatycznego usuwania starych blokad.
Richard Hansen
W przypadku dwustopniowego odblokowania ( mv, rm) należy rm -fzastosować, a nie rmdwa wyścigi P1, P2? Na przykład P1 rozpoczyna odblokowywanie mv, następnie P2 blokuje, następnie P2 odblokowuje (oba mvi rm), w końcu P1 próbuje rmi kończy się niepowodzeniem.
Matt Wallis,
1
@MattWallis Ten ostatni problem można łatwo rozwiązać, dołączając go $$do ${f}.deletemenazwy pliku.
Stefan Majewsky
23
Inną opcją jest użycie noclobberopcji powłoki przez uruchomienie set -C. Wtedy >się nie powiedzie, jeśli plik już istnieje.
W skrócie:
set-C
lockfile="/tmp/locktest.lock"if echo "$$">"$lockfile";then
echo "Successfully acquired lock"# do work
rm "$lockfile"# XXX or via trap - see belowelse
echo "Cannot acquire lock - already locked by $(cat "$lockfile")"fi
To powoduje, że powłoka wywołuje:
open(pathname, O_CREAT|O_EXCL)
który atomowo tworzy plik lub kończy się niepowodzeniem, jeśli plik już istnieje.
Zgodnie z komentarzem do BashFAQ 045 , to może się nie powieść ksh88, ale działa we wszystkich moich powłokach:
Bardzo nowatorskie podejście ... wydaje się, że jest to jeden ze sposobów na osiągnięcie atomowości za pomocą pliku blokady, a nie katalogu blokady.
Matt Caldwell
Niezłe podejście. :-) W pułapce WYJŚCIE powinien ograniczać, który proces może wyczyścić plik blokady. Na przykład: trap 'if [[$ (cat „$ lockfile”) == „$$”]]; następnie rm „$ lockfile”; fi 'EXIT
Kevin Seifert
1
Pliki blokady nie są atomowe w systemie plików NFS. dlatego ludzie zaczęli korzystać z blokad katalogów.
K Richard Pixley,
20
Możesz użyć GNU Paralleldo tego, ponieważ działa jako mutex, gdy jest wywoływany jako sem. Konkretnie możesz użyć:
sem --id SCRIPTSINGLETON yourScript
Jeśli chcesz również przekroczyć limit czasu, użyj:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Limit czasu <0 oznacza wyjście bez uruchamiania skryptu, jeśli semafor nie zostanie zwolniony w ramach limitu czasu, limit czasu> 0 oznacza uruchomienie skryptu i tak.
Pamiętaj, że powinieneś nadać mu nazwę (z --id), w przeciwnym razie domyślnie będzie to terminal kontrolny.
GNU Parallel to bardzo prosta instalacja na większości platform Linux / OSX / Unix - to tylko skrypt Perla.
Szkoda, że ludzie niechętnie odrzucają bezużyteczne odpowiedzi: prowadzi to do tego, że nowe stosowne odpowiedzi są zakopywane w kupie śmieci.
Dmitry Grigoryev
4
Potrzebujemy tylko wielu pozytywnych opinii. To taka schludna i mało znana odpowiedź. (Choć pedantyczny OP chciał być szybki i brudny, podczas gdy jest to szybki i czysty!) Więcej na sempowiązane pytanie unix.stackexchange.com/a/322200/199525 .
Częściowe zachmurzenie
16
W przypadku skryptów powłoki zwykle używam mkdirwięcej, flockponieważ zamki są bardziej przenośne.
Tak czy inaczej, używanie set -enie wystarczy. To kończy działanie skryptu tylko w przypadku niepowodzenia dowolnego polecenia. Twoje zamki pozostaną w tyle.
Aby poprawnie wyczyścić blokadę, naprawdę powinieneś ustawić pułapki na coś takiego jak ten kod psuedo (podniesiony, uproszczony i nieprzetestowany, ale z aktywnie używanych skryptów):
#=======================================================================# Predefined Global Variables#=======================================================================
TMPDIR=/tmp/myapp
[[!-d $TMP_DIR ]] \
&& mkdir -p $TMP_DIR \
&& chmod 700 $TMPDIR
LOCK_DIR=$TMP_DIR/lock
#=======================================================================# Functions#=======================================================================function mklock {
__lockdir="$LOCK_DIR/$(date +%s.%N).$$"# Private Global. Use Epoch.Nano.PID# If it can create $LOCK_DIR then no other instance is runningif $(mkdir $LOCK_DIR)then
mkdir $__lockdir # create this instance's specific lock in queue
LOCK_EXISTS=true # Globalelse
echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
exit 1001# Or work out some sleep_while_execution_lock elsewherefi}function rmlock {[[!-d $__lockdir ]] \
&& echo "WARNING: Lock is missing. $__lockdir does not exist" \
|| rmdir $__lockdir
}#-----------------------------------------------------------------------# Private Signal Traps Functions {{{2## DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or # there will be *NO CLEAN UP*. You'll have to manually remove # any locks in place.#-----------------------------------------------------------------------function __sig_exit {# Place your clean up logic here # Remove the LOCK[[-n $LOCK_EXISTS ]]&& rmlock
}function __sig_int {
echo "WARNING: SIGINT caught"
exit 1002}function __sig_quit {
echo "SIGQUIT caught"
exit 1003}function __sig_term {
echo "WARNING: SIGTERM caught"
exit 1015}#=======================================================================# Main#=======================================================================# Set TRAPs
trap __sig_exit EXIT # SIGEXIT
trap __sig_int INT # SIGINT
trap __sig_quit QUIT # SIGQUIT
trap __sig_term TERM # SIGTERM
mklock
# CODE
exit # No need for cleanup code here being in the __sig_exit trap function
Oto co się stanie. Wszystkie pułapki spowodują wyjście, więc funkcja __sig_exitbędzie zawsze działać (z wyjątkiem SIGKILL), który czyści twoje zamki.
Uwaga: moje wartości wyjściowe nie są wartościami niskimi. Czemu? Różne systemy przetwarzania wsadowego określają lub mają oczekiwania dotyczące liczb od 0 do 31. Ustawiając je na coś innego, mogę mieć moje skrypty i strumienie wsadowe odpowiednio reagujące na poprzednie zadanie wsadowe lub skrypt.
Twój skrypt jest zbyt szczegółowy, myślę, że mógł być o wiele krótszy, ale ogólnie rzecz biorąc, tak, musisz ustawić pułapki, aby zrobić to poprawnie. Dodałbym również SIGHUP.
mojuba
Działa to dobrze, ale wydaje się, że sprawdza $ LOCK_DIR, podczas gdy usuwa $ __ lockdir. Może powinienem zasugerować przy usuwaniu blokady rm -r $ LOCK_DIR?
bevada
Dziękuję za sugestię. Powyższy kod został zniesiony i umieszczony w stylu kodu psuedo, więc będzie wymagał strojenia w zależności od użycia ludzi. Jednak celowo poszedłem z rmdir w moim przypadku, ponieważ rmdir bezpiecznie usuwa katalogi tylko jeśli są puste. Jeśli ludzie umieszczają w nich zasoby, takie jak pliki PID itp., Powinni zmienić czyszczenie blokady na bardziej agresywne rm -r $LOCK_DIRlub nawet wymusić to w razie potrzeby (tak jak zrobiłem to również w szczególnych przypadkach, takich jak przechowywanie względnych plików zdrapek). Twoje zdrowie.
Mark Stinson,
Testowałeś exit 1002?
Gilles Quenot
13
Naprawdę szybki i naprawdę brudny? Ta linijka na górze skryptu będzie działać:
[[ $(pgrep -c "`basename \"$0\"`")-gt 1]]&& exit
Oczywiście upewnij się, że nazwa skryptu jest unikalna. :)
Jak mogę to zasymulować, aby to przetestować? Czy istnieje sposób na uruchomienie skryptu dwa razy w jednym wierszu i może otrzymać ostrzeżenie, jeśli już działa?
rubo77
2
To w ogóle nie działa! Po co sprawdzać -gt 2? grep nie zawsze znajduje się w wyniku ps!
rubo77
pgrepnie ma w POSIX. Jeśli chcesz, aby działało to przenośnie, potrzebujesz POSIX psi przetwarzaj jego dane wyjściowe.
Palec
Na OSX -cnie istnieje, będziesz musiał użyć | wc -l. O porównaniu liczb: -gt 1jest sprawdzane, ponieważ pierwsza instancja widzi siebie.
Benjamin Peter,
6
Oto podejście, które łączy atomowe blokowanie katalogu z sprawdzaniem, czy blokada jest przestarzała przez PID i restart, jeśli jest nieaktualna. Nie opiera się to również na żadnych baszizmach.
#!/bin/dash
SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"if! mkdir $LOCKDIR 2>/dev/null
then# lock failed, but check for stale one by checking if the PID is really existing
PID=$(cat $PIDFILE)if! kill -0 $PID 2>/dev/null
then
echo "Removing stale lock of nonexistent PID ${PID}">&2
rm -rf $LOCKDIR
echo "Restarting myself (${SCRIPTNAME})">&2
exec "$0""$@"fi
echo "$SCRIPTNAME is already running, bailing out">&2
exit 1else# lock successfully acquired, save PID
echo $$ > $PIDFILE
fi
trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echo bye
Utworzyć plik blokady w znanej lokalizacji i sprawdzić, czy istnieje na początku skryptu? Umieszczenie PID w pliku może być pomocne, jeśli ktoś próbuje wyśledzić błędną instancję, która uniemożliwia wykonanie skryptu.
Ten przykład jest wyjaśniony w man flock, ale wymaga pewnych ulepszeń, ponieważ powinniśmy zarządzać błędami i kodami wyjścia:
#!/bin/bash#set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.(#start subprocess# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10200if["$?"!="0"];then echo Cannot lock!; exit 1;fi
echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom ) 200>/var/lock/.myscript.exclusivelock.# Do stuff# you can properly manage exit codes with multiple command and process algorithm.# I suggest throw this all to external procedure than can properly handle exit X commands)200>/var/lock/.myscript.exclusivelock #exit subprocess
FLOCKEXIT=$?#save exitcode status#do some finish commands
exit $FLOCKEXIT #return properly exitcode, may be usefull inside external scripts
Możesz użyć innej metody, wymień procesy, których użyłem w przeszłości. Jest to jednak bardziej skomplikowane niż powyższa metoda. Powinieneś wymienić procesy według ps, filtrować według jego nazwy, dodatkowy filtr grep -v grep dla usunięcia pasożyta i na końcu policzyć go przez grep -c. i porównaj z liczbą. To skomplikowane i niepewne
Możesz użyć ln -s, ponieważ może to tworzyć dowiązanie symboliczne tylko wtedy, gdy nie istnieje plik lub dowiązanie symboliczne, tak samo jak mkdir. w przeszłości wiele procesów systemowych używało dowiązań symbolicznych, na przykład init lub inetd. synlink zachowuje identyfikator procesu, ale tak naprawdę wskazuje na nic. przez lata to zachowanie się zmieniło. procesy wykorzystuje stada i semafory.
Znik
5
Istniejące opublikowane odpowiedzi albo polegają na narzędziu CLI, flockalbo nie zabezpieczają poprawnie pliku blokady. Narzędzie flock nie jest dostępne we wszystkich systemach innych niż Linux (tj. FreeBSD) i nie działa poprawnie w systemie plików NFS.
Na początku administrowania systemem i rozwoju systemu powiedziano mi, że bezpieczną i względnie przenośną metodą tworzenia pliku blokady jest utworzenie pliku tymczasowego przy użyciu mkemp(3)lub mkemp(1)zapisanie informacji identyfikujących w pliku tymczasowym (tj. PID), a następnie twardego łącza plik tymczasowy do pliku blokady. Jeśli połączenie się powiodło, oznacza to, że blokada została pomyślnie uzyskana.
Kiedy używam blokad w skryptach powłoki, zwykle umieszczam obtain_lock()funkcję we wspólnym profilu, a następnie pozyskuję ją ze skryptów. Poniżej znajduje się przykład mojej funkcji blokady:
obtain_lock(){
LOCK="${1}"
LOCKDIR="$(dirname "${LOCK}")"
LOCKFILE="$(basename "${LOCK}")"# create temp lock file
TMPLOCK=$(mktemp -p "${LOCKDIR}""${LOCKFILE}XXXXXX"2>/dev/null)if test "x${TMPLOCK}"=="x";then
echo "unable to create temporary file with mktemp"1>&2return1fi
echo "$$">"${TMPLOCK}"# attempt to obtain lock file
ln "${TMPLOCK}""${LOCK}"2>/dev/null
if test $?-ne 0;then
rm -f "${TMPLOCK}"
echo "unable to obtain lockfile"1>&2if test -f "${LOCK}";then
echo "current lock information held by: $(cat "${LOCK}")"1>&2fireturn2fi
rm -f "${TMPLOCK}"return0;};
Oto przykład korzystania z funkcji blokady:
#!/bin/sh./path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"
clean_up(){
rm -f "${PROG_LOCKFILE}"}
obtain_lock "${PROG_LOCKFILE}"if test $?-ne 0;then
exit 1fi
trap clean_up SIGHUP SIGINT SIGTERM
# bulk of script
clean_up
exit 0# end of script
Pamiętaj, aby zadzwonić clean_updo dowolnego punktu wyjścia w skrypcie.
Podczas celowania w maszynę Debiana uważam, że lockfile-progspakiet jest dobrym rozwiązaniem. procmailjest również wyposażony w lockfilenarzędzie. Czasami jednak utknąłem z żadnym z nich.
Oto moje rozwiązanie, które wykorzystuje mkdiratomowość i plik PID do wykrywania starych blokad. Ten kod jest obecnie produkowany w konfiguracji Cygwin i działa dobrze.
Aby go użyć, po prostu zadzwoń, exclusive_lock_requiregdy potrzebujesz wyłącznego dostępu do czegoś. Opcjonalny parametr nazwy blokady pozwala udostępniać blokady między różnymi skryptami. Istnieją również dwie funkcje niższego poziomu ( exclusive_lock_tryi exclusive_lock_retry), jeśli potrzebujesz czegoś bardziej złożonego.
function exclusive_lock_try()# [lockname]{local LOCK_NAME="${1:-`basename $0`}"
LOCK_DIR="/tmp/.${LOCK_NAME}.lock"local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"if[-e "$LOCK_DIR"]thenlocal LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"if[!-z "$LOCK_PID"]&& kill -0"$LOCK_PID"2>/dev/null
then# locked by non-dead process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1else# orphaned lock, take it over( echo $$ >"$LOCK_PID_FILE")2>/dev/null &&local LOCK_PID="$$"fifiif["`trap -p EXIT`"!=""]then# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"return1fiif["$LOCK_PID"!="$$"]&&!( umask 077&& mkdir "$LOCK_DIR"&& umask 177&& echo $$ >"$LOCK_PID_FILE")2>/dev/null
thenlocal LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"# unable to acquire lock, new process got in first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return0# got lock}function exclusive_lock_retry()# [lockname] [retries] [delay]{local LOCK_NAME="$1"local MAX_TRIES="${2:-5}"local DELAY="${3:-2}"local TRIES=0local LOCK_RETVAL
while["$TRIES"-lt "$MAX_TRIES"]doif["$TRIES"-gt 0]then
sleep "$DELAY"filocal TRIES=$(( $TRIES +1))if["$TRIES"-lt "$MAX_TRIES"]then
exclusive_lock_try "$LOCK_NAME">/dev/null
else
exclusive_lock_try "$LOCK_NAME"fi
LOCK_RETVAL="${PIPESTATUS[0]}"if["$LOCK_RETVAL"-eq 0]thenreturn0fidonereturn"$LOCK_RETVAL"}function exclusive_lock_require()# [lockname] [retries] [delay]{if! exclusive_lock_retry "$@"then
exit 1fi}
Dzięki, sam wypróbowałem to na cygwin i przeszło proste testy.
ndemou
4
Jeśli ograniczenia stada, które zostały już opisane w innym miejscu tego wątku, nie stanowią dla ciebie problemu, to powinno działać:
#!/bin/bash{# exit if we are unable to obtain a lock; this would happen if # the script is already running elsewhere# note: -x (exclusive) is the default
flock -n 100|| exit
# put commands to run here
sleep 100}100>/tmp/myjob.lock
Pomyślałem, że zwrócę uwagę, że -x (blokada zapisu) jest już ustawiona domyślnie.
Keldon Alleyne,
i -nzrobi to exit 1natychmiast, jeśli nie będzie w stanie uzyskać blokady
Anentropic
Dzięki @KeldonAlleyne zaktualizowałem kod, aby usunąć „-x”, ponieważ jest on domyślny.
presto8
3
Niektóre uniksy mają lockfilebardzo podobne do już wspomnianych flock.
Z strony podręcznika:
Pliku blokującego można użyć do utworzenia jednego lub więcej plików semaforów. Jeśli plik blokujący nie może utworzyć wszystkich określonych plików (w podanej kolejności), czeka na czas snu (domyślnie 8) sekund i ponawia próbę ostatniego pliku, który się nie powiódł. Możesz określić liczbę ponownych prób do momentu powrotu błędu. Jeśli liczba ponownych prób wynosi -1 (domyślnie, tj. -R-1) plik blokujący spróbuje ponownie na zawsze.
lockfilejest rozpowszechniany z procmail. Istnieje również alternatywa dotlockfiledla liblockfilepakietu. Obaj twierdzą, że działają niezawodnie na NFS.
Mr. Deathless,
3
W rzeczywistości, chociaż odpowiedź na bmdhacks jest prawie dobra, istnieje niewielka szansa, że drugi skrypt zostanie uruchomiony po pierwszym sprawdzeniu pliku blokującego i przed jego napisaniem. Obaj napiszą więc plik blokady i będą działać. Oto jak sprawić, by na pewno działało:
lockfile=/var/lock/myscript.lock
if(set-o noclobber; echo "$$">"$lockfile")2>/dev/null ;then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else# or you can decide to skip the "else" part if you want
echo "Another instance is already running!"fi
Ta noclobberopcja sprawi, że polecenie przekierowania zakończy się niepowodzeniem, jeśli plik już istnieje. Tak więc polecenie przekierowania jest w rzeczywistości atomowe - piszesz i sprawdzasz plik za pomocą jednego polecenia. Nie musisz usuwać pliku blokującego na końcu pliku - zostanie on usunięty przez pułapkę. Mam nadzieję, że pomoże to ludziom, którzy przeczytają to później.
PS Nie widziałem, że Mikel już poprawnie odpowiedział na pytanie, chociaż nie uwzględnił polecenia pułapki, aby zmniejszyć prawdopodobieństwo pozostawienia pliku blokady po zatrzymaniu skryptu na przykład za pomocą Ctrl-C. To jest kompletne rozwiązanie
Chciałem pozbyć się plików blokujących, lockdirów, specjalnych programów blokujących, a nawet pidofponieważ nie znaleziono go we wszystkich instalacjach Linuksa. Chciałem również mieć możliwie najprostszy kod (lub przynajmniej jak najmniej wierszy). Najprostsze ifzdanie, w jednym wierszu:
Jest to wrażliwe na wyjście „ps” na moim komputerze (Ubuntu 14.04, / bin / ps z procps-ng wersja 3.3.9) polecenie „ps axf” drukuje znaki drzewa ascii, które zakłócają numery pól. To działało dla mnie: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill
2
Używam prostego podejścia, które obsługuje nieaktualne pliki blokad.
Zauważ, że niektóre z powyższych rozwiązań, które przechowują pid, ignorują fakt, że pid może się owijać. Tak więc - samo sprawdzenie, czy istnieje prawidłowy proces z przechowywanym pid, nie wystarczy, szczególnie w przypadku długo działających skryptów.
Używam noclobber, aby upewnić się, że tylko jeden skrypt może się otwierać i zapisywać do pliku blokady jednocześnie. Ponadto przechowuję wystarczającą ilość informacji, aby jednoznacznie zidentyfikować proces w pliku blokady. Definiuję zestaw danych, aby jednoznacznie zidentyfikować proces, który ma być pid, ppid, lstart.
Po uruchomieniu nowego skryptu, jeśli nie uda mu się utworzyć pliku blokady, następnie sprawdza, czy proces, który utworzył plik blokady, nadal istnieje. Jeśli nie, zakładamy, że pierwotny proces zmarł niemiłą śmiercią i pozostawił nieaktualny plik blokady. Nowy skrypt przejmuje własność pliku blokady i znów wszystko jest dobrze na świecie.
Powinien działać z wieloma powłokami na wielu platformach. Szybki, przenośny i prosty.
#!/usr/bin/env sh# Author: rouble
LOCKFILE=/var/tmp/lockfile #customize this line
trap release INT TERM EXIT
# Creates a lockfile. Sets global variable $ACQUIRED to true on success.# # Returns 0 if it is successfully able to create lockfile.
acquire (){set-C #Shell noclobber option. If file exists, > will fail.
UUID=`ps -eo pid,ppid,lstart $$ | tail -1`if(echo "$UUID">"$LOCKFILE")2>/dev/null;then
ACQUIRED="TRUE"return0elseif[-e $LOCKFILE ];then# We may be dealing with a stale lock file.# Bring out the magnifying glass.
CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`if["$CURRENT_UUID_FROM_LOCKFILE"=="$CURRENT_UUID_FROM_PS"];then
echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE">&2return1else# The process that created this lock file died an ungraceful death. # Take ownership of the lock file.
echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
release "FORCE"if(echo "$UUID">"$LOCKFILE")2>/dev/null;then
ACQUIRED="TRUE"return0else
echo "Cannot write to $LOCKFILE. Error.">&2return1fifielse
echo "Do you have write permissons to $LOCKFILE ?">&2return1fifi}# Removes the lock file only if this script created it ($ACQUIRED is set), # OR, if we are removing a stale lock file (first parameter is "FORCE")
release (){#Destroy lock file. Take no prisoners.if["$ACQUIRED"]||["$1"=="FORCE"];then
rm -f $LOCKFILE
fi}# Test code# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if[ $?-eq 0];then
echo "Acquired lock."
read -p "Press [Enter] key to release lock..."
release
echo "Released lock."else
echo "Unable to acquire lock."fi
Dałem ci +1 za inne rozwiązanie. Mimo to nie działa ani w systemie AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: niepoprawna lista z opcją -o.) Nie HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: niedozwolona opcja - o). Dzięki.
["${FLOCKER}"!="$0"]&&{ echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi";}|| echo "Lock is free. Completing."
Ustawia i sprawdza zamki za pomocą flocknarzędzia. Ten kod wykrywa, czy został uruchomiony po raz pierwszy, sprawdzając zmienną FLOCKER, jeśli nie jest ustawiony na nazwę skryptu, a następnie próbuje ponownie uruchomić skrypt rekurencyjnie przy użyciu flocka i przy zainicjowanej zmiennej FLOCKER, jeśli FLOCKER jest ustawiony poprawnie, następnie flock na poprzedniej iteracji powiodło się i można kontynuować. Jeśli blokada jest zajęta, kończy się niepowodzeniem z konfigurowalnym kodem wyjścia.
Wygląda na to, że nie działa na Debianie 7, ale wydaje się, że działa ponownie z eksperymentalnym pakietem util-linux 2.25. Zapisuje „flock: ... Plik tekstowy zajęty”. Można to zmienić, wyłączając uprawnienia do zapisu w skrypcie.
PID i pliki blokujące są zdecydowanie najbardziej niezawodne. Podczas próby uruchomienia programu może on sprawdzić plik blokujący, który, a jeśli istnieje, może użyć, psaby sprawdzić, czy proces nadal działa. Jeśli tak nie jest, skrypt może się uruchomić, aktualizując PID w pliku blokady do własnego.
Uważam, że rozwiązanie bmdhack jest najbardziej praktyczne, przynajmniej w moim przypadku użycia. Używanie flocka i pliku blokującego polega na usunięciu pliku blokującego przy użyciu rm po zakończeniu skryptu, co nie zawsze jest gwarantowane (np. Zabić -9).
Chciałbym zmienić jedną drobną rzecz dotyczącą rozwiązania bmdhack: ma sens usunięcie pliku blokady, bez stwierdzenia, że nie jest to konieczne do bezpiecznego działania tego semafora. Jego użycie kill -0 zapewnia, że stary plik blokujący dla martwego procesu zostanie po prostu zignorowany / nadpisany.
Dlatego moim uproszczonym rozwiązaniem jest dodanie następujących elementów na górze singletonu:
## Test the lock
LOCKFILE=/tmp/singleton.lock
if[-e ${LOCKFILE}]&& kill -0`cat ${LOCKFILE}`;then
echo "Script already running. bye!"
exit
fi## Set the lock
echo $$ > ${LOCKFILE}
Oczywiście ten skrypt nadal ma tę wadę, że procesy, które prawdopodobnie rozpoczną się w tym samym czasie, stanowią zagrożenie wyścigu, ponieważ testy blokady i operacje ustawiania nie są pojedynczym działaniem atomowym. Ale proponowane przez lhunath rozwiązanie polegające na użyciu mkdir ma wadę polegającą na tym, że zabity skrypt może pozostawić katalog, uniemożliwiając w ten sposób uruchomienie innych instancji.
Narzędzie semaforyczne wykorzystuje flock(jak omówiono powyżej, np. Przez presto8) do implementacji semafora zliczającego . Umożliwia dowolną określoną liczbę współbieżnych procesów, jakie chcesz. Używamy go do ograniczania poziomu współbieżności różnych procesów roboczych kolejki.
To jest jak sem, ale o wiele lżejsze. (Pełne ujawnienie: napisałem to po stwierdzeniu, że sem był zbyt ciężki na nasze potrzeby i nie było dostępne proste narzędzie do liczenia semaforów).
Przykład z flock (1), ale bez podpowłoki. Plik flock () ed / tmp / foo nigdy nie jest usuwany, ale to nie ma znaczenia, ponieważ pobiera flock () i un-flock () ed.
#!/bin/bash
exec 9<>/tmp/foo
flock -n 9
RET=$?if[[ $RET -ne 0]];then
echo "lock failed, exiting"
exit
fi#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&-#close fd 9, and release lock#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
Bez wywołania pułapki (lub przynajmniej czyszczenia w pobliżu końca w normalnym przypadku) masz fałszywie dodatni błąd, w którym plik blokujący pozostaje po ostatnim uruchomieniu, a PID został ponownie wykorzystany przez inny proces później. (A w najgorszym przypadku został obdarzony długim procesem, takim jak apache ....)
Philippe Chaintreuil
1
Zgadzam się, moje podejście jest wadliwe, potrzebuje pułapki. Zaktualizowałem swoje rozwiązanie. Nadal wolę nie mieć zewnętrznych zależności.
Filidor Wiese
1
Korzystanie z blokady procesu jest znacznie silniejsze i dba również o nieżyczliwe wyjścia. plik_zablokowany jest otwarty tak długo, jak długo trwa proces. Zostanie zamknięty (przez powłokę), gdy proces będzie istniał (nawet jeśli zostanie zabity). Uważam, że jest to bardzo wydajne:
lock_file=/tmp/`basename $0`.lock
if fuser $lock_file >/dev/null 2>&1;then
echo "WARNING: Other instance of $(basename $0) running."
exit 1fi
exec 3> $lock_file
Ścieżka trzody jest właściwą drogą. Zastanów się, co się stanie, gdy skrypt nagle umrze. W przypadku stada po prostu tracisz stado, ale to nie jest problem. Zauważ też, że zła sztuczka polega na tym, że stada samego skryptu ... ale to oczywiście pozwala na pełną parą naprzód problemy z uprawnieniami.
Może to być problem, ale nie musi, w zależności od sposobu jego użycia, ale istnieje warunek wyścigu między testowaniem zamka i jego tworzeniem, aby oba skrypty mogły być uruchomione jednocześnie. Jeśli jedno zostanie zakończone jako pierwsze, drugie pozostanie uruchomione bez pliku blokady.
TimB
3
Wiadomości C, które nauczyły mnie wiele o przenośnych skryptach powłoki, zrobiły plik blokady. $$, a następnie próbowały połączyć go z „blokadą” - jeśli link się powiedzie, masz blokadę, w przeciwnym razie usuniesz blokadę. $$ i wyszedł.
Paul Tomblin,
To naprawdę dobry sposób, aby to zrobić, ale nadal musisz ręcznie usunąć plik blokujący, jeśli coś pójdzie nie tak i plik blokujący nie zostanie usunięty.
Odpowiedzi:
Oto implementacja, która korzysta z pliku blokującego i wprowadza do niego echo PID. Służy to jako ochrona, jeśli proces zostanie zabity przed usunięciem pliku pid :
Sztuczka polega na
kill -0
tym, że nie dostarcza żadnego sygnału, a jedynie sprawdza, czy istnieje proces o danym PID. Również wywołanietrap
zapewni, że plik blokujący zostanie usunięty, nawet jeśli proces zostanie zabity (z wyjątkiemkill -9
).źródło
Użyj,
flock(1)
aby zablokować wyłączny zakres deskryptora pliku. W ten sposób możesz nawet synchronizować różne części skryptu.Zapewnia to, że kod pomiędzy
(
i)
jest uruchamiany tylko przez jeden proces na raz i że proces nie czeka zbyt długo na blokadę.Zastrzeżenie: to konkretne polecenie jest częścią
util-linux
. Jeśli używasz systemu operacyjnego innego niż Linux, może on być dostępny lub może nie być dostępny.źródło
( command A ) command B
wywołuje podpowłokęcommand A
. Dokumentowane na tldp.org/LDP/abs/html/subshells.html . Nadal nie jestem pewien terminu wywołania podpowłoki i polecenia B.if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
tak, aby w przypadku przekroczenia limitu czasu (jakiś inny proces zablokował plik) skrypt ten nie działał i nie modyfikował pliku. Prawdopodobnie ... kontrargumentem jest „ale jeśli zajęło to 10 sekund, a blokada nadal nie jest dostępna, nigdy nie będzie dostępna”, prawdopodobnie dlatego, że proces trzymający blokadę nie kończy się (być może jest uruchamiany pod debuggerem?).exit
Jest od wewnętrznej części(
)
. Po zakończeniu podprocesu blokada jest automatycznie zwalniana, ponieważ nie wstrzymuje go żaden proces.Wszystkie podejścia testujące istnienie „plików blokujących” są wadliwe.
Czemu? Ponieważ nie ma sposobu, aby sprawdzić, czy plik istnieje i utworzyć go w pojedynczej akcji atomowej. Z tego powodu; istnieje warunek wyścigu, który spowoduje, że twoje próby wzajemnego wykluczenia zostaną przerwane.
Zamiast tego musisz użyć
mkdir
.mkdir
tworzy katalog, jeśli jeszcze nie istnieje, a jeśli tak, ustawia kod wyjścia. Co ważniejsze, robi to wszystko w jednej akcji atomowej, dzięki czemu idealnie nadaje się do tego scenariusza.Aby uzyskać wszystkie szczegóły, zobacz doskonałe BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Jeśli chcesz zająć się przestarzałymi zamkami, przydaje się utrwalacz (1) . Jedynym minusem jest to, że operacja trwa około sekundy, więc nie jest natychmiastowa.
Oto funkcja, którą napisałem kiedyś, która rozwiązuje problem z użyciem utrwalacza:
Możesz użyć go w skrypcie takim jak:
Jeśli nie zależy ci na przenośności (te rozwiązania powinny działać prawie na każdym systemie UNIX), fuser Linuksa (1) oferuje kilka dodatkowych opcji, a także flock (1) .
źródło
if ! mkdir
część ze sprawdzeniem, czy proces z PID zapisanym (przy pomyślnym uruchomieniu) wewnątrz lockdir faktycznie działa i jest identyczny ze skryptem do ochrony stalenes. Chroniłoby to również przed ponownym użyciem PID po ponownym uruchomieniu, a nawet nie wymagałobyfuser
.mkdir
nie jest zdefiniowana jako operacja atomowa i jako taka „efekt uboczny” jest szczegółową implementacją systemu plików. W pełni mu wierzę, jeśli mówi, że NFS nie wdraża go w sposób atomowy. Chociaż nie podejrzewam, że/tmp
będziesz częścią NFS i zapewne dostarczy go fs, który implementujemkdir
atomowo.ln
utworzyć twarde łącze z innego pliku. Jeśli masz dziwne systemy plików, które tego nie gwarantują, możesz później sprawdzić i-węzeł nowego pliku, aby sprawdzić, czy jest on taki sam jak oryginalny plik.open(... O_CREAT|O_EXCL)
. Potrzebujesz do tego odpowiedniego programu użytkownika, takiego jaklockfile-create
(inlockfile-progs
) lubdotlockfile
(inliblockfile-bin
). I upewnij się, że odpowiednio wyczyściłeś (np.trap EXIT
) Lub przetestowałeś nieaktualne zamki (np. Z--use-pid
).Wokół wywołania systemowego flock (2) znajduje się opakowanie nazywane, niewyobrażalnie, flock (1). To sprawia, że względnie łatwo jest uzyskać wyłączne blokady bez obawy o oczyszczenie itp. Na stronie podręcznika znajdują się przykłady użycia go w skrypcie powłoki.
źródło
flock()
Wywołanie systemowe nie jest POSIX i nie działa dla plików na NFS.flock -x -n %lock file% -c "%command%"
z zadania Crona, używam do upewnienia się, że tylko jedna instancja jest wykonywana.Potrzebujesz operacji atomowej, takiej jak stado, inaczej to się ostatecznie nie powiedzie.
Ale co zrobić, jeśli stado nie jest dostępne. Cóż, jest mkdir. To także operacja atomowa. Tylko jeden proces zakończy się powodzeniem mkdir, wszystkie inne zakończą się niepowodzeniem.
Więc kod to:
Musisz zająć się przestarzałymi blokadami po awarii, skrypt nigdy nie uruchomi się ponownie.
źródło
sleep 10
wcześniejrmdir
i spróbuj ponownie kaskadowo - nic nie „wycieknie”.Aby blokowanie było niezawodne, potrzebujesz operacji atomowej. Wiele z powyższych propozycji nie ma charakteru atomowego. Proponowane narzędzie lockfile (1) wygląda obiecująco, jak wspomniała strona podręcznika, że jest „odporne na NFS”. Jeśli twój system operacyjny nie obsługuje pliku blokującego (1), a twoje rozwiązanie musi działać w systemie plików NFS, masz niewiele opcji ...
NFSv2 ma dwie operacje atomowe:
W NFSv3 wywołanie create jest również atomowe.
Operacje na katalogach NIE są atomowe pod NFSv2 i NFSv3 (patrz książka „NFS Illustrated” Brenta Callaghana, ISBN 0-201-32570-5; Brent jest weteranem NFS w Sun).
Wiedząc o tym, możesz zaimplementować blokady spinów dla plików i katalogów (w powłoce, a nie PHP):
zablokuj bieżący katalog:
zablokuj plik:
odblokuj bieżący katalog (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
odblokuj plik (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
Usuń również nie ma charakteru atomowego, dlatego najpierw należy zmienić nazwę (która jest atomowa), a następnie usunąć.
W przypadku wywołań dowiązania symbolicznego i zmiany nazwy oba nazwy plików muszą znajdować się w tym samym systemie plików. Moja propozycja: użyj tylko prostych nazw plików (bez ścieżek) i umieść plik i zablokuj w tym samym katalogu.
źródło
lockfile
jeśli jest dostępna, lub powrotu do tejsymlink
metody, jeśli nie.mv
,rm
) należyrm -f
zastosować, a nierm
dwa wyścigi P1, P2? Na przykład P1 rozpoczyna odblokowywaniemv
, następnie P2 blokuje, następnie P2 odblokowuje (obamv
irm
), w końcu P1 próbujerm
i kończy się niepowodzeniem.$$
do${f}.deleteme
nazwy pliku.Inną opcją jest użycie
noclobber
opcji powłoki przez uruchomienieset -C
. Wtedy>
się nie powiedzie, jeśli plik już istnieje.W skrócie:
To powoduje, że powłoka wywołuje:
który atomowo tworzy plik lub kończy się niepowodzeniem, jeśli plik już istnieje.
Zgodnie z komentarzem do BashFAQ 045 , to może się nie powieść
ksh88
, ale działa we wszystkich moich powłokach:Interesujące, że
pdksh
dodajeO_TRUNC
flagę, ale oczywiście jest zbędne:albo tworzysz pusty plik, albo nic nie robisz.
To, jak to zrobisz,
rm
zależy od tego, jak chcesz obsłużyć nieczyste wyjścia.Usuń przy czystym wyjściu
Nowe uruchomienia kończą się niepowodzeniem, dopóki problem, który spowodował rozwiązanie nieczystego wyjścia, nie zostanie usunięty ręcznie.
Usuń przy każdym wyjściu
Nowe uruchomienia zakończą się powodzeniem, pod warunkiem, że skrypt jeszcze nie działa.
źródło
Możesz użyć
GNU Parallel
do tego, ponieważ działa jako mutex, gdy jest wywoływany jakosem
. Konkretnie możesz użyć:Jeśli chcesz również przekroczyć limit czasu, użyj:
Limit czasu <0 oznacza wyjście bez uruchamiania skryptu, jeśli semafor nie zostanie zwolniony w ramach limitu czasu, limit czasu> 0 oznacza uruchomienie skryptu i tak.
Pamiętaj, że powinieneś nadać mu nazwę (z
--id
), w przeciwnym razie domyślnie będzie to terminal kontrolny.GNU Parallel
to bardzo prosta instalacja na większości platform Linux / OSX / Unix - to tylko skrypt Perla.źródło
sem
powiązane pytanie unix.stackexchange.com/a/322200/199525 .W przypadku skryptów powłoki zwykle używam
mkdir
więcej,flock
ponieważ zamki są bardziej przenośne.Tak czy inaczej, używanie
set -e
nie wystarczy. To kończy działanie skryptu tylko w przypadku niepowodzenia dowolnego polecenia. Twoje zamki pozostaną w tyle.Aby poprawnie wyczyścić blokadę, naprawdę powinieneś ustawić pułapki na coś takiego jak ten kod psuedo (podniesiony, uproszczony i nieprzetestowany, ale z aktywnie używanych skryptów):
Oto co się stanie. Wszystkie pułapki spowodują wyjście, więc funkcja
__sig_exit
będzie zawsze działać (z wyjątkiem SIGKILL), który czyści twoje zamki.Uwaga: moje wartości wyjściowe nie są wartościami niskimi. Czemu? Różne systemy przetwarzania wsadowego określają lub mają oczekiwania dotyczące liczb od 0 do 31. Ustawiając je na coś innego, mogę mieć moje skrypty i strumienie wsadowe odpowiednio reagujące na poprzednie zadanie wsadowe lub skrypt.
źródło
rm -r $LOCK_DIR
lub nawet wymusić to w razie potrzeby (tak jak zrobiłem to również w szczególnych przypadkach, takich jak przechowywanie względnych plików zdrapek). Twoje zdrowie.exit 1002
?Naprawdę szybki i naprawdę brudny? Ta linijka na górze skryptu będzie działać:
Oczywiście upewnij się, że nazwa skryptu jest unikalna. :)
źródło
-gt 2
? grep nie zawsze znajduje się w wyniku ps!pgrep
nie ma w POSIX. Jeśli chcesz, aby działało to przenośnie, potrzebujesz POSIXps
i przetwarzaj jego dane wyjściowe.-c
nie istnieje, będziesz musiał użyć| wc -l
. O porównaniu liczb:-gt 1
jest sprawdzane, ponieważ pierwsza instancja widzi siebie.Oto podejście, które łączy atomowe blokowanie katalogu z sprawdzaniem, czy blokada jest przestarzała przez PID i restart, jeśli jest nieaktualna. Nie opiera się to również na żadnych baszizmach.
źródło
Utworzyć plik blokady w znanej lokalizacji i sprawdzić, czy istnieje na początku skryptu? Umieszczenie PID w pliku może być pomocne, jeśli ktoś próbuje wyśledzić błędną instancję, która uniemożliwia wykonanie skryptu.
źródło
Ten przykład jest wyjaśniony w man flock, ale wymaga pewnych ulepszeń, ponieważ powinniśmy zarządzać błędami i kodami wyjścia:
Możesz użyć innej metody, wymień procesy, których użyłem w przeszłości. Jest to jednak bardziej skomplikowane niż powyższa metoda. Powinieneś wymienić procesy według ps, filtrować według jego nazwy, dodatkowy filtr grep -v grep dla usunięcia pasożyta i na końcu policzyć go przez grep -c. i porównaj z liczbą. To skomplikowane i niepewne
źródło
Istniejące opublikowane odpowiedzi albo polegają na narzędziu CLI,
flock
albo nie zabezpieczają poprawnie pliku blokady. Narzędzie flock nie jest dostępne we wszystkich systemach innych niż Linux (tj. FreeBSD) i nie działa poprawnie w systemie plików NFS.Na początku administrowania systemem i rozwoju systemu powiedziano mi, że bezpieczną i względnie przenośną metodą tworzenia pliku blokady jest utworzenie pliku tymczasowego przy użyciu
mkemp(3)
lubmkemp(1)
zapisanie informacji identyfikujących w pliku tymczasowym (tj. PID), a następnie twardego łącza plik tymczasowy do pliku blokady. Jeśli połączenie się powiodło, oznacza to, że blokada została pomyślnie uzyskana.Kiedy używam blokad w skryptach powłoki, zwykle umieszczam
obtain_lock()
funkcję we wspólnym profilu, a następnie pozyskuję ją ze skryptów. Poniżej znajduje się przykład mojej funkcji blokady:Oto przykład korzystania z funkcji blokady:
Pamiętaj, aby zadzwonić
clean_up
do dowolnego punktu wyjścia w skrypcie.Użyłem powyższego w środowiskach Linux i FreeBSD.
źródło
Podczas celowania w maszynę Debiana uważam, że
lockfile-progs
pakiet jest dobrym rozwiązaniem.procmail
jest również wyposażony wlockfile
narzędzie. Czasami jednak utknąłem z żadnym z nich.Oto moje rozwiązanie, które wykorzystuje
mkdir
atomowość i plik PID do wykrywania starych blokad. Ten kod jest obecnie produkowany w konfiguracji Cygwin i działa dobrze.Aby go użyć, po prostu zadzwoń,
exclusive_lock_require
gdy potrzebujesz wyłącznego dostępu do czegoś. Opcjonalny parametr nazwy blokady pozwala udostępniać blokady między różnymi skryptami. Istnieją również dwie funkcje niższego poziomu (exclusive_lock_try
iexclusive_lock_retry
), jeśli potrzebujesz czegoś bardziej złożonego.źródło
Jeśli ograniczenia stada, które zostały już opisane w innym miejscu tego wątku, nie stanowią dla ciebie problemu, to powinno działać:
źródło
-n
zrobi toexit 1
natychmiast, jeśli nie będzie w stanie uzyskać blokadyNiektóre uniksy mają
lockfile
bardzo podobne do już wspomnianychflock
.Z strony podręcznika:
źródło
lockfile
narzędzie?lockfile
jest rozpowszechniany zprocmail
. Istnieje również alternatywadotlockfile
dlaliblockfile
pakietu. Obaj twierdzą, że działają niezawodnie na NFS.W rzeczywistości, chociaż odpowiedź na bmdhacks jest prawie dobra, istnieje niewielka szansa, że drugi skrypt zostanie uruchomiony po pierwszym sprawdzeniu pliku blokującego i przed jego napisaniem. Obaj napiszą więc plik blokady i będą działać. Oto jak sprawić, by na pewno działało:
Ta
noclobber
opcja sprawi, że polecenie przekierowania zakończy się niepowodzeniem, jeśli plik już istnieje. Tak więc polecenie przekierowania jest w rzeczywistości atomowe - piszesz i sprawdzasz plik za pomocą jednego polecenia. Nie musisz usuwać pliku blokującego na końcu pliku - zostanie on usunięty przez pułapkę. Mam nadzieję, że pomoże to ludziom, którzy przeczytają to później.PS Nie widziałem, że Mikel już poprawnie odpowiedział na pytanie, chociaż nie uwzględnił polecenia pułapki, aby zmniejszyć prawdopodobieństwo pozostawienia pliku blokady po zatrzymaniu skryptu na przykład za pomocą Ctrl-C. To jest kompletne rozwiązanie
źródło
Chciałem pozbyć się plików blokujących, lockdirów, specjalnych programów blokujących, a nawet
pidof
ponieważ nie znaleziono go we wszystkich instalacjach Linuksa. Chciałem również mieć możliwie najprostszy kod (lub przynajmniej jak najmniej wierszy). Najprostszeif
zdanie, w jednym wierszu:źródło
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Używam prostego podejścia, które obsługuje nieaktualne pliki blokad.
Zauważ, że niektóre z powyższych rozwiązań, które przechowują pid, ignorują fakt, że pid może się owijać. Tak więc - samo sprawdzenie, czy istnieje prawidłowy proces z przechowywanym pid, nie wystarczy, szczególnie w przypadku długo działających skryptów.
Używam noclobber, aby upewnić się, że tylko jeden skrypt może się otwierać i zapisywać do pliku blokady jednocześnie. Ponadto przechowuję wystarczającą ilość informacji, aby jednoznacznie zidentyfikować proces w pliku blokady. Definiuję zestaw danych, aby jednoznacznie zidentyfikować proces, który ma być pid, ppid, lstart.
Po uruchomieniu nowego skryptu, jeśli nie uda mu się utworzyć pliku blokady, następnie sprawdza, czy proces, który utworzył plik blokady, nadal istnieje. Jeśli nie, zakładamy, że pierwotny proces zmarł niemiłą śmiercią i pozostawił nieaktualny plik blokady. Nowy skrypt przejmuje własność pliku blokady i znów wszystko jest dobrze na świecie.
Powinien działać z wieloma powłokami na wielu platformach. Szybki, przenośny i prosty.
źródło
Dodaj ten wiersz na początku skryptu
Jest to kod z bojlera od stada człowieka.
Jeśli chcesz więcej rejestrowania, użyj tego
Ustawia i sprawdza zamki za pomocą
flock
narzędzia. Ten kod wykrywa, czy został uruchomiony po raz pierwszy, sprawdzając zmienną FLOCKER, jeśli nie jest ustawiony na nazwę skryptu, a następnie próbuje ponownie uruchomić skrypt rekurencyjnie przy użyciu flocka i przy zainicjowanej zmiennej FLOCKER, jeśli FLOCKER jest ustawiony poprawnie, następnie flock na poprzedniej iteracji powiodło się i można kontynuować. Jeśli blokada jest zajęta, kończy się niepowodzeniem z konfigurowalnym kodem wyjścia.Wygląda na to, że nie działa na Debianie 7, ale wydaje się, że działa ponownie z eksperymentalnym pakietem util-linux 2.25. Zapisuje „flock: ... Plik tekstowy zajęty”. Można to zmienić, wyłączając uprawnienia do zapisu w skrypcie.
źródło
PID i pliki blokujące są zdecydowanie najbardziej niezawodne. Podczas próby uruchomienia programu może on sprawdzić plik blokujący, który, a jeśli istnieje, może użyć,
ps
aby sprawdzić, czy proces nadal działa. Jeśli tak nie jest, skrypt może się uruchomić, aktualizując PID w pliku blokady do własnego.źródło
Uważam, że rozwiązanie bmdhack jest najbardziej praktyczne, przynajmniej w moim przypadku użycia. Używanie flocka i pliku blokującego polega na usunięciu pliku blokującego przy użyciu rm po zakończeniu skryptu, co nie zawsze jest gwarantowane (np. Zabić -9).
Chciałbym zmienić jedną drobną rzecz dotyczącą rozwiązania bmdhack: ma sens usunięcie pliku blokady, bez stwierdzenia, że nie jest to konieczne do bezpiecznego działania tego semafora. Jego użycie kill -0 zapewnia, że stary plik blokujący dla martwego procesu zostanie po prostu zignorowany / nadpisany.
Dlatego moim uproszczonym rozwiązaniem jest dodanie następujących elementów na górze singletonu:
Oczywiście ten skrypt nadal ma tę wadę, że procesy, które prawdopodobnie rozpoczną się w tym samym czasie, stanowią zagrożenie wyścigu, ponieważ testy blokady i operacje ustawiania nie są pojedynczym działaniem atomowym. Ale proponowane przez lhunath rozwiązanie polegające na użyciu mkdir ma wadę polegającą na tym, że zabity skrypt może pozostawić katalog, uniemożliwiając w ten sposób uruchomienie innych instancji.
źródło
Narzędzie semaforyczne wykorzystuje
flock
(jak omówiono powyżej, np. Przez presto8) do implementacji semafora zliczającego . Umożliwia dowolną określoną liczbę współbieżnych procesów, jakie chcesz. Używamy go do ograniczania poziomu współbieżności różnych procesów roboczych kolejki.To jest jak sem, ale o wiele lżejsze. (Pełne ujawnienie: napisałem to po stwierdzeniu, że sem był zbyt ciężki na nasze potrzeby i nie było dostępne proste narzędzie do liczenia semaforów).
źródło
Przykład z flock (1), ale bez podpowłoki. Plik flock () ed / tmp / foo nigdy nie jest usuwany, ale to nie ma znaczenia, ponieważ pobiera flock () i un-flock () ed.
źródło
Odpowiedział już milion razy, ale w inny sposób, bez potrzeby zewnętrznych zależności:
Za każdym razem zapisuje bieżący PID ($$) w pliku blokady, a przy uruchomieniu skryptu sprawdza, czy proces działa z najnowszym PID.
źródło
Korzystanie z blokady procesu jest znacznie silniejsze i dba również o nieżyczliwe wyjścia. plik_zablokowany jest otwarty tak długo, jak długo trwa proces. Zostanie zamknięty (przez powłokę), gdy proces będzie istniał (nawet jeśli zostanie zabity). Uważam, że jest to bardzo wydajne:
źródło
Używam oneliner @ od samego początku skryptu:
Dobrze jest widzieć obecność procesu w pamięci (bez względu na status procesu); ale robi to za mnie.
źródło
Ścieżka trzody jest właściwą drogą. Zastanów się, co się stanie, gdy skrypt nagle umrze. W przypadku stada po prostu tracisz stado, ale to nie jest problem. Zauważ też, że zła sztuczka polega na tym, że stada samego skryptu ... ale to oczywiście pozwala na pełną parą naprzód problemy z uprawnieniami.
źródło
Szybko i brudno?
źródło