Вот реализация, которая использует файл блокировки и отображает в нем PID. Это служит защитой, если процесс завершается перед удалением pidfile :
LOCKFILE=/tmp/lock.txtif[-e ${LOCKFILE}]&& kill -0`cat ${LOCKFILE}`;then
echo "already running"
exitfi# 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).
Как уже упоминалось в комментарии к другому ответу, у этого есть фатальный недостаток - если другой скрипт запускается между проверкой и эхом, вы тост.
Пол Томблин
1
Трюк с символьными ссылками хорош, но если владельцем файла блокировки является kill -9'd или система падает, все еще существует условие гонки, чтобы прочитать символическую ссылку, заметить, что владелец пропал, а затем удалить его. Я придерживаюсь своего решения.
bmdhacks
9
Атомная проверка и создание доступны в оболочке с помощью flock (1) или lockfile (1). Смотрите другие ответы.
dmckee --- котенок экс-модератора
3
Смотрите мой ответ для портативного способа выполнения атомарной проверки и создания без необходимости полагаться на такие утилиты, как flock или lockfile.
лунат
2
Это не атомарно и поэтому бесполезно. Вам нужен атомарный механизм для тестирования и установки.
К Ричард Пиксли
214
Используйте, flock(1)чтобы сделать эксклюзивную блокировку области действия для файлового дескриптора. Таким образом, вы можете даже синхронизировать различные части скрипта.
#!/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
Это гарантирует, что код между (и )выполняется только одним процессом за раз, и что процесс не будет слишком долго ждать блокировки.
Предостережение: эта конкретная команда является частью util-linux. Если вы используете операционную систему, отличную от Linux, она может быть или не быть доступной.
Что такое 200? В мануле написано «fd», но я не знаю, что это значит.
Чови
4
@chovy "дескриптор файла", целочисленный дескриптор, обозначающий открытый файл.
Alex B
6
Если кому-то еще интересно: синтаксис ( command A ) command Bвызывает подоболочку для command A. Документально подтвержден по адресу tldp.org/LDP/abs/html/subshells.html . Я все еще не уверен в сроках вызова подоболочки и команды B.
Доктор Ян-Филипп Герке
1
Я думаю, что код внутри суб-оболочки должен быть больше похожим: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fiтак что, если происходит тайм-аут (какой-то другой процесс заблокировал файл), этот сценарий не идет вперед и не изменяет файл. Вероятно ... контраргумент: «но если это заняло 10 секунд, а блокировка все еще недоступна, она никогда не будет доступна», предположительно потому, что процесс, удерживающий блокировку, не завершается (возможно, он выполняется под отладчиком?).
Джонатан Леффлер
1
Файл, к которому перенаправлен, является только папкой для действия блокировки, в него не поступает значимых данных. Это exitот части внутри (). Когда подпроцесс завершается, блокировка автоматически снимается, потому что процесс не удерживает ее.
клак
158
Все подходы, которые проверяют существование «файлов блокировки», имеют недостатки.
Зачем? Потому что нет способа проверить, существует ли файл и создать его в одном атомарном действии. Из-за этого; есть условие гонки, которое БУДЕТ сделать ваши попытки взаимного исключения разорвать.
Вместо этого вам нужно использовать mkdir. mkdirсоздает каталог, если он еще не существует, и если он существует, он устанавливает код выхода. Что еще более важно, он делает все это в одном атомном действии, что делает его идеальным для этого сценария.
Если вы хотите позаботиться об устаревших замках, фьюзер (1) пригодится. Единственным недостатком здесь является то, что операция занимает около секунды, поэтому она не мгновенная.
Вот функция, которую я написал однажды, которая решает проблему с помощью fuser:
# 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}
Если вас не волнует переносимость (эти решения должны работать практически на любой UNIX-системе), Linux fuser (1) предлагает некоторые дополнительные опции, а также flock (1) .
Вы можете комбинировать if ! mkdirдеталь с проверкой, действительно ли процесс с PID, сохраненным (при успешном запуске) внутри lockdir, действительно работает и идентичен сценарию для защиты стандартизации. Это также защитит от повторного использования PID после перезагрузки и даже не потребует fuser.
Тобиас Кинцлер,
4
Это, безусловно, верно, что mkdirне определено, чтобы быть атомарной операцией и как таковой, что «побочный эффект» является деталью реализации файловой системы. Я полностью верю ему, если он говорит, что NFS не реализует это атомарно. Хотя я не подозреваю, что у вас /tmpбудет общий ресурс NFS, и он, вероятно, будет предоставлен fs, который реализует mkdirатомарно.
июня
5
Но есть способ проверить наличие обычного файла и создать его атомарно, если это не так: использовать lnдля создания жесткой ссылки из другого файла. Если у вас есть странные файловые системы, которые не гарантируют этого, вы можете проверить inode нового файла, чтобы узнать, совпадает ли он с исходным файлом.
Хуан Сеспедес
4
Там является «способ проверить , существует ли файл и создать его в одном атомарном действии» - это open(... O_CREAT|O_EXCL). Вам просто нужна подходящая пользовательская программа, например, lockfile-create(in lockfile-progs) или dotlockfile(in liblockfile-bin). И убедитесь, что вы чистите правильно (например trap EXIT), или проверьте на устаревшие замки (например, с --use-pid).
Тоби Спейт
5
«Все подходы, которые проверяют существование« файлов блокировки », несовершенны. Почему? Потому что нет способа проверить, существует ли файл и создать его в одном атомарном действии». - Чтобы сделать его атомарным, это должно быть сделано в на уровне ядра - и это делается на уровне ядра с помощью flock (1) linux.die.net/man/1/flock, который, как видно из даты авторского права человека, существует примерно с 2006 года. Поэтому я сделал понижающее голосование (- 1) ничего личного, просто твердо убежден, что использование инструментов, реализованных в ядре разработчиками ядра, является правильным.
Крейг Хикс
42
Вокруг системного вызова flock (2) есть обертка, которая невообразимо называется flock (1). Это позволяет относительно легко получить эксклюзивные блокировки, не беспокоясь об очистке и т. Д. На странице руководства приведены примеры использования этой функции в сценарии оболочки.
flock()Системный вызов POSIX и не работает для файлов на монтирует NFS.
maxschlepzig
17
Запуск из задания Cron, которое я использую, flock -x -n %lock file% -c "%command%"чтобы убедиться, что выполняется только один экземпляр.
Ryall
О, вместо невообразимой паствы (1) они должны были пойти с чем-то вроде стаи (U). ... у него есть некоторое знакомство с этим. , Кажется, я слышал это раньше или через два.
Кент Крукеберг
Примечательно, что документация flock (2) определяет использование только с файлами, а документация flock (1) определяет использование с файлом или каталогом. В документации flock (1) не указано, как указать разницу при создании, но я предполагаю, что это делается путем добавления окончательного символа "/". В любом случае, если flock (1) может обрабатывать каталоги, а flock (2) - нет, то flock (1) не реализуется только в flock (2).
Крейг Хикс,
27
Вам нужна атомарная операция, например, flock, иначе это в конечном итоге не удастся.
Но что делать, если стадо недоступно. Ну, есть Mkdir. Это тоже атомарная операция. Только один процесс приведет к успешному выполнению mkdir, все остальные потерпят неудачу.
Итак, код:
if mkdir /var/lock/.myscript.exclusivelockthen# do stuff:
rmdir /var/lock/.myscript.exclusivelockfi
Вам нужно позаботиться об устаревших блокировках, иначе после сбоя ваш скрипт никогда не запустится снова.
Выполните это несколько раз одновременно (например, "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") и скрипт пропустит несколько раз.
Nippysaurus
7
@Nippysaurus: этот метод блокировки не протекает. То, что вы видели, было завершением начального сценария до запуска всех копий, поэтому другой смог (правильно) получить блокировку. Чтобы избежать этого ложного срабатывания, добавьте sleep 10перед rmdirи попробуйте снова каскадировать - ничто не будет «просачиваться».
Сэр Афон
Другие источники утверждают, что mkdir не является атомарным в некоторых файловых системах, таких как NFS. И, между прочим, я видел случаи, когда в NFS одновременная рекурсивная MKDIR иногда приводила к ошибкам с матричными заданиями Дженкинса. Так что я уверен, что это так. Но mkdir довольно хорош для менее требовательных случаев использования IMO.
Акостадинов
Вы можете использовать опцию Bash'es noclobber с обычными файлами.
Palec
26
Чтобы сделать блокировку надежной, вам нужна атомарная операция. Многие из вышеперечисленных предложений не являются атомарными. Предложенная утилита lockfile (1) выглядит многообещающе, поскольку на странице руководства упоминается, что она «устойчива к NFS». Если ваша ОС не поддерживает lockfile (1) и ваше решение должно работать на NFS, у вас не так много вариантов ....
NFSv2 имеет две атомарные операции:
символическая
переименование
В NFSv3 вызов create также является атомарным.
Операции с каталогами НЕ являются атомарными в NFSv2 и NFSv3 (см. Книгу «Иллюстрированный NFS» Брента Каллагана, ISBN 0-201-32570-5; Брент - ветеран NFS в Sun).
Зная это, вы можете реализовать спин-блокировки для файлов и каталогов (в оболочке, а не в PHP):
заблокировать текущий каталог:
while! ln -s . lock;do:;done
заблокировать файл:
while! ln -s ${f} ${f}.lock;do:;done
unlock current dir (предположим, что запущенный процесс действительно получил блокировку):
mv lock deleteme && rm deleteme
разблокировать файл (предположим, что запущенный процесс действительно получил блокировку):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Удалить также не атомарный, поэтому сначала переименуйте (который является атомным), а затем удалить.
Для вызовов symlink и rename оба имени файла должны находиться в одной файловой системе. Мое предложение: использовать только простые имена файлов (без путей) и поместить файл и заблокировать в том же каталоге.
Какие страницы NFS Illustrated поддерживают утверждение, что mkdir не является атомарным по сравнению с NFS?
maxschlepzig
Спасибо за эту технику. Реализация мьютекса оболочки доступна в моей новой оболочке lib: github.com/Offirmo/offirmo-shell-lib , см. «Мьютекс». Используется, lockfileесли доступно, или откат к этому symlinkметоду, если нет.
Offirmo
Ницца. К сожалению, этот метод не позволяет автоматически удалять устаревшие блокировки.
Ричард Хансен
Для двухэтапной разблокировки ( mv, rm) следует rm -fиспользовать, а не rmв случае гонок двух процессов P1, P2? Например, P1 начинает разблокировку с mv, затем P2 блокирует, затем P2 разблокирует (оба mvи rm), в конце концов P1 пытается rmи терпит неудачу.
Мэтт Уоллис
1
@MattWallis Эта последняя проблема может быть легко устранена путем включения $$в ${f}.deletemeимя файла.
Стефан Маевский
23
Другой вариант - использовать 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 belowelse
echo "Cannot acquire lock - already locked by $(cat "$lockfile")"fi
Это заставляет оболочку вызывать:
open(pathname, O_CREAT|O_EXCL)
который атомарно создает файл или терпит неудачу, если файл уже существует.
Согласно комментарию к BashFAQ 045 , это может не сработать ksh88, но оно работает во всех моих оболочках:
Очень новый подход ... кажется, это один из способов достижения атомарности с использованием файла блокировки, а не каталога блокировки.
Мэтт Колдуэлл
Хороший подход. :-) В ловушке EXIT должно быть указано, какой процесс может очистить файл блокировки. Например: trap 'if [[$ (cat "$ lockfile") == "$$"]]; затем rm "$ lockfile"; fi 'EXIT
Кевин Сейферт
1
Файлы блокировки не являются атомарными по сравнению с NFS. Вот почему люди перешли на использование каталогов блокировки.
К Ричард Пиксли,
20
Вы можете использовать GNU Parallelэто как мьютекс при вызове как sem. Итак, в конкретных терминах, вы можете использовать:
sem --id SCRIPTSINGLETON yourScript
Если вы тоже хотите установить тайм-аут, используйте:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Тайм-аут <0 означает выход без выполнения скрипта, если семафор не был выпущен в течение тайм-аута, тайм-аут> 0 означает, что скрипт все равно будет запущен.
Обратите внимание, что вы должны дать ему имя (с --id), иначе он по умолчанию будет управляющим терминалом.
GNU Parallel это очень простая установка на большинстве платформ Linux / OSX / Unix - это всего лишь скрипт на Perl.
Жаль, что люди не хотят принизить бесполезные ответы: это приводит к тому, что новые важные ответы будут похоронены в куче мусора.
Дмитрий Григорьев
4
Нам просто нужно много голосов. Это такой аккуратный и малоизвестный ответ. (Хотя для того, чтобы быть педантичным, ОП хотел быстро и грязно, тогда как это быстро и чисто!) Подробнее на semсмежный вопрос unix.stackexchange.com/a/322200/199525 .
Небольшая
16
Для сценариев оболочки, я , как правило, идут с mkdirболее , flockкак это делает замки более переносимым.
В любом случае, использования set -eнедостаточно. Это выходит из сценария только в случае сбоя какой-либо команды. Ваши замки все равно останутся позади.
Для правильной очистки блокировки вы действительно должны установить свои ловушки на что-то вроде этого кода psuedo (поднятого, упрощенного и непроверенного, но из активно используемых скриптов):
#=======================================================================# 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
Вот что будет. Все ловушки будут создавать выход, поэтому функция __sig_exitвсегда будет выполняться (за исключением SIGKILL), которая очищает ваши блокировки.
Примечание: мои значения выхода не являются низкими значениями. Зачем? Различные системы пакетной обработки делают или имеют ожидания от чисел от 0 до 31. Установив их на что-то другое, я могу заставить свои сценарии и потоки пакетов реагировать соответственно на предыдущее пакетное задание или сценарий.
Ваш сценарий слишком многословен, я думаю, он мог бы быть намного короче, но в целом, да, вы должны настроить ловушки, чтобы сделать это правильно. Также я бы добавил SIGHUP.
Моджуба
Это работает хорошо, за исключением того, что он проверяет $ LOCK_DIR, тогда как он удаляет $ __ lockdir. Может быть, я должен предложить при снятии блокировки сделать rm -r $ LOCK_DIR?
Бевада
Спасибо за предложение. Выше был снят код и размещен в виде кода псевдо, так что он будет нуждаться в настройке, основанной на использовании людей. Тем не менее, я намеренно пошел с rmdir в моем случае, так как rmdir безопасно удаляет директории only_, если они пусты. Если люди размещают в них ресурсы, такие как PID-файлы и т. Д., Они должны изменить свою очистку блокировки на более агрессивную rm -r $LOCK_DIRили даже принудительно применить ее по мере необходимости (как я это сделал в особых случаях, например, при хранении относительных файлов с нулями). Приветствия.
Марк Стинсон
Вы проверяли exit 1002?
Жиль Квено
13
Действительно быстро и действительно грязно? Эта однострочная строка в верхней части вашего скрипта будет работать:
[[ $(pgrep -c "`basename \"$0\"`")-gt 1]]&& exit
Конечно, просто убедитесь, что имя вашего скрипта уникально. :)
Как мне смоделировать это, чтобы проверить это? Есть ли способ запустить скрипт дважды в одной строке и, возможно, получить предупреждение, если он уже запущен?
rubo77 21.09.16
2
Это не работает вообще! Зачем проверять -gt 2? grep не всегда оказывается в результате ps!
rubo77
pgrepне в POSIX. Если вы хотите, чтобы это работало переносимо, вам нужен POSIX psи обработать его вывод.
Палек
На OSX -cне существует, вам придется использовать | wc -l. О сравнении чисел: -gt 1проверяется, так как первый экземпляр видит себя.
Бенджамин Питер
6
Вот подход, который объединяет атомарную блокировку каталогов с проверкой устаревшей блокировки через PID и перезапускает, если устарел. Кроме того, это не зависит от каких-либо нарушений.
#!/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
Создать файл блокировки в известном месте и проверить его существование при запуске скрипта? Поместить PID в файл может быть полезно, если кто-то пытается отследить ошибочный экземпляр, препятствующий выполнению сценария.
Этот пример объясняется в man flock, но он требует некоторых улучшений, потому что мы должны управлять ошибками и кодами выхода:
#!/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
Вы можете использовать другой метод, перечислить процессы, которые я использовал в прошлом. Но это сложнее, чем метод выше. Вы должны перечислить процессы по ps, отфильтровать по его имени, дополнительный фильтр grep -v grep для удаления паразита и, наконец, считать его по grep -c. и сравните с номером. Это сложно и неопределенно
Вы можете использовать ln -s, потому что это может создать символическую ссылку только тогда, когда нет файла или символической ссылки, так же, как mkdir. в прошлом многие системные процессы использовали символические ссылки, например init или inetd. synlink сохраняет идентификатор процесса, но на самом деле ничего не указывает. за эти годы это поведение изменилось. процессы используют стада и семафоры.
Znik
5
Опубликованные существующие ответы либо используют утилиту CLI, flockлибо неправильно защищают файл блокировки. Утилита flock доступна не во всех системах, отличных от Linux (например, FreeBSD), и не работает должным образом в NFS.
В первые годы системного администрирования и разработки системы мне говорили, что безопасным и относительно переносимым способом создания файла блокировки было создание временного файла с использованием mkemp(3)или mkemp(1), запись идентифицирующей информации во временный файл (т. Е. PID), затем жесткая ссылка. временный файл к файлу блокировки. Если ссылка была успешной, значит, вы успешно получили блокировку.
При использовании блокировок в сценариях оболочки я обычно помещаю obtain_lock()функцию в общий профиль и затем извлекаю ее из сценариев. Ниже приведен пример моей функции блокировки:
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;};
Ниже приведен пример использования функции блокировки:
#!/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
Не забудьте звонить clean_upв любой точке выхода в вашем сценарии.
Ориентируясь на компьютер Debian, я считаю lockfile-progsпакет хорошим решением. procmailтакже поставляется с lockfileинструментом. Однако иногда я застреваю ни с одним из них.
Вот мое решение, которое использует mkdirатомарность и PID-файл для обнаружения устаревших блокировок. Этот код в настоящее время находится в производстве на установке Cygwin и работает хорошо.
Чтобы использовать его просто позвоните, exclusive_lock_requireкогда вам нужно получить эксклюзивный доступ к чему-либо. Необязательный параметр имени блокировки позволяет разделять блокировки между различными сценариями. Также есть две функции более низкого уровня ( exclusive_lock_tryи exclusive_lock_retry), если вам нужно что-то более сложное.
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}
Спасибо, попробовал это на cygwin сам, и это прошло простые тесты.
ndemou
4
Если ограничения стада, которые уже были описаны в этом разделе, не являются для вас проблемой, тогда это должно сработать:
#!/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
Просто подумал, что укажу, что -x (блокировка записи) уже установлена по умолчанию.
Келдон Аллейн
и -nбудет exit 1сразу же , если он не может получить блокировку
Anentropic
Спасибо @KeldonAlleyne, я обновил код, чтобы удалить "-x", так как это по умолчанию.
presto8
3
У некоторых юниксов есть lockfileчто очень похоже на уже упомянутое flock.
Из справочной страницы:
lockfile может быть использован для создания одного или нескольких файлов семафоров. Если lock-файл не может создать все указанные файлы (в указанном порядке), он ожидает время сна (по умолчанию 8) секунд и повторяет последний файл, который не удалось. Вы можете указать количество повторных попыток до тех пор, пока ошибка не будет возвращена. Если число повторных попыток равно -1 (по умолчанию, то есть -r-1), lockfile будет повторяться бесконечно.
lockfileраспространяется с procmail. Также есть альтернатива, dotlockfileкоторая идет с liblockfileпакетом. Они оба утверждают, что надежно работают на NFS.
Мистер Бессмертный
3
На самом деле, хотя ответ bmdhacks почти хороший, есть небольшой шанс, что второй скрипт запустится после первой проверки файла блокировки и до того, как он его написал. Таким образом, они оба напишут файл блокировки, и они оба будут работать. Вот как заставить это работать наверняка:
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
noclobberВариант будет убедиться , что редирект команда потерпит неудачу , если файл уже существует. Таким образом, команда перенаправления на самом деле атомарна - вы пишете и проверяете файл одной командой. Вам не нужно удалять файл блокировки в конце файла - он будет удален ловушкой. Я надеюсь, что это поможет людям, которые будут читать это позже.
PS Я не видел, что Микель уже правильно ответил на вопрос, хотя он не включил команду trap, чтобы уменьшить вероятность того, что файл блокировки останется после остановки сценария, например, с помощью Ctrl-C. Так что это полное решение
Я хотел покончить с lockfiles, lockdirs, специальными программами блокировки и даже pidofпотому, что он не найден во всех установках Linux. Также хотелось иметь максимально простой код (или, по крайней мере, как можно меньше строк). Простейшее ifутверждение в одну строку:
Это чувствительно к выводу 'ps', на моей машине (Ubuntu 14.04, / bin / ps из procps-ng версии 3.3.9) команда 'ps axf' печатает символы дерева ascii, которые разрушают номера полей. Это сработало для меня: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Qneill
2
Я использую простой подход, который обрабатывает устаревшие файлы блокировки.
Обратите внимание, что некоторые из приведенных выше решений, в которых хранится пид, игнорируют тот факт, что пид может обернуться вокруг. Итак, просто проверить, существует ли допустимый процесс с сохраненным pid, недостаточно, особенно для долго выполняющихся сценариев.
Я использую noclobber, чтобы убедиться, что только один скрипт может открывать и записывать в файл блокировки одновременно. Кроме того, я храню достаточно информации, чтобы однозначно идентифицировать процесс в файле блокировки. Я определяю набор данных, чтобы однозначно идентифицировать процесс, который будет pid, ppid, lstart.
Когда запускается новый скрипт, если он не может создать файл блокировки, он затем проверяет, что процесс, который создал файл блокировки, все еще существует. Если нет, мы предполагаем, что первоначальный процесс умер из-за неуместной смерти и оставил файл устаревшей блокировки. Затем новый сценарий становится владельцем файла блокировки, и снова все в порядке.
Должен работать с несколькими оболочками на разных платформах. Быстро, портативно и просто.
#!/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
Я дал тебе +1 за другое решение. Хотя это и не работает ни в AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: неверный список с -o.), Но не HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: недопустимый вариант - o). Спасибо.
Если вы хотите больше регистрации, используйте этот
["${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."
Это устанавливает и проверяет блокировки с помощью flockутилиты. Этот код обнаруживает, запускался ли он впервые, проверяя переменную FLOCKER, если для него не задано имя сценария, затем он пытается рекурсивно запустить сценарий снова с использованием flock и с инициализированной переменной FLOCKER, если FLOCKER установлен правильно, а затем выполнить flock на предыдущей итерации. удалось, и это нормально, чтобы продолжить. Если блокировка занята, она терпит неудачу с настраиваемым кодом выхода.
Кажется, он не работает на Debian 7, но, похоже, снова работает с экспериментальным пакетом util-linux 2.25. Он пишет "flock: ... Text file busy". Это может быть отменено путем отключения разрешения на запись в вашем скрипте.
PID и lockfiles, безусловно, самые надежные. Когда вы пытаетесь запустить программу, она может проверить файл блокировки, который и, если он существует, он может использовать, psчтобы увидеть, работает ли еще процесс. Если это не так, скрипт может запуститься, обновив PID в файле блокировки до своего собственного.
Я считаю, что решение bmdhack является наиболее практичным, по крайней мере, для моего случая использования. Использование flock и lockfile основывается на удалении lockfile с использованием rm, когда скрипт завершается, что не всегда может быть гарантировано (например, kill -9).
Я хотел бы изменить одну незначительную вещь в решении bmdhack: он имеет смысл удалить файл блокировки, не заявляя, что это не нужно для безопасной работы этого семафора. Его использование kill -0 гарантирует, что старый файл блокировки для мертвого процесса будет просто проигнорирован / перезаписан.
Поэтому мое упрощенное решение состоит в том, чтобы просто добавить следующее в начало вашего синглтона:
## 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}
Конечно, у этого скрипта все еще есть недостаток, заключающийся в том, что процессы, которые могут запускаться одновременно, имеют опасность гонки, поскольку тест блокировки и операции установки не являются единичным атомарным действием. Но предлагаемое решение для этого с помощью lhunath для использования mkdir имеет недостаток, заключающийся в том, что убитый скрипт может оставить каталог, таким образом предотвращая запуск других экземпляров.
В semaphoric утилита использует flock(как описано выше, например , с помощью presto8) для осуществления подсчета семафора . Это позволяет любое конкретное количество параллельных процессов, которые вы хотите. Мы используем его для ограничения уровня параллелизма различных рабочих процессов очереди.
Это похоже на сем, но гораздо легче. (Полное раскрытие: я написал это после того, как обнаружил, что sem слишком тяжел для наших нужд, и не было простой утилиты для подсчета семафоров.)
Пример с flock (1), но без подоболочки. Файл flock () ed / tmp / foo никогда не удаляется, но это не имеет значения, так как он получает flock () и 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
Без вызова прерывания (или, по крайней мере, очистки в конце для обычного случая), вы получаете ложно-положительную ошибку, в которой файл блокировки остается после последнего запуска, а PID повторно используется другим процессом позже. (И в худшем случае это был одаренный длительный процесс, такой как apache ....)
Филипп Chaintreuil
1
Я согласен, мой подход некорректен, ему нужна ловушка. Я обновил свое решение. Я все еще предпочитаю не иметь внешних зависимостей.
Филидор Визе
1
Использование блокировки процесса намного сильнее и заботится о неблаговидных выходах. lock_file остается открытым, пока процесс запущен. Он будет закрыт (оболочкой), как только процесс будет существовать (даже если его убьют). Я нашел это очень эффективным:
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
Путь стада - путь. Подумайте о том, что происходит, когда сценарий внезапно умирает. В случае с стадом вы просто теряете стадо, но это не проблема. Кроме того, обратите внимание, что злой трюк состоит в том, чтобы наброситься на сам сценарий ... но это, конечно, позволяет вам полностью справиться с проблемами разрешения.
Это может или не может быть проблемой, в зависимости от того, как она используется, но существует условие состязания между тестированием блокировки и ее созданием, так что два сценария могут быть запущены одновременно. Если один завершится первым, другой продолжит работу без файла блокировки.
TimB
3
C News, которая многому научила меня в сценариях переносимых оболочек, использовала для блокировки файл. $$, а затем пыталась связать его с помощью «lock» - если ссылка удалась, у вас была блокировка, в противном случае вы сняли блокировку. $$ и вышел.
Пол Томблин
Это действительно хороший способ сделать это, за исключением того, что вы по-прежнему испытываете необходимость удалить файл блокировки вручную, если что-то пойдет не так и файл блокировки не будет удален.
Ответы:
Вот реализация, которая использует файл блокировки и отображает в нем PID. Это служит защитой, если процесс завершается перед удалением pidfile :
Хитрость здесь в том,
kill -0
что она не доставляет никакого сигнала, а просто проверяет, существует ли процесс с данным PID. Кроме того, вызовtrap
гарантирует, что файл блокировки будет удален, даже если ваш процесс убит (кромеkill -9
).источник
Используйте,
flock(1)
чтобы сделать эксклюзивную блокировку области действия для файлового дескриптора. Таким образом, вы можете даже синхронизировать различные части скрипта.Это гарантирует, что код между
(
и)
выполняется только одним процессом за раз, и что процесс не будет слишком долго ждать блокировки.Предостережение: эта конкретная команда является частью
util-linux
. Если вы используете операционную систему, отличную от Linux, она может быть или не быть доступной.источник
( command A ) command B
вызывает подоболочку дляcommand A
. Документально подтвержден по адресу tldp.org/LDP/abs/html/subshells.html . Я все еще не уверен в сроках вызова подоболочки и команды B.if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
так что, если происходит тайм-аут (какой-то другой процесс заблокировал файл), этот сценарий не идет вперед и не изменяет файл. Вероятно ... контраргумент: «но если это заняло 10 секунд, а блокировка все еще недоступна, она никогда не будет доступна», предположительно потому, что процесс, удерживающий блокировку, не завершается (возможно, он выполняется под отладчиком?).exit
от части внутри(
)
. Когда подпроцесс завершается, блокировка автоматически снимается, потому что процесс не удерживает ее.Все подходы, которые проверяют существование «файлов блокировки», имеют недостатки.
Зачем? Потому что нет способа проверить, существует ли файл и создать его в одном атомарном действии. Из-за этого; есть условие гонки, которое БУДЕТ сделать ваши попытки взаимного исключения разорвать.
Вместо этого вам нужно использовать
mkdir
.mkdir
создает каталог, если он еще не существует, и если он существует, он устанавливает код выхода. Что еще более важно, он делает все это в одном атомном действии, что делает его идеальным для этого сценария.Для всех деталей, смотрите отличный BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Если вы хотите позаботиться об устаревших замках, фьюзер (1) пригодится. Единственным недостатком здесь является то, что операция занимает около секунды, поэтому она не мгновенная.
Вот функция, которую я написал однажды, которая решает проблему с помощью fuser:
Вы можете использовать его в скрипте так:
Если вас не волнует переносимость (эти решения должны работать практически на любой UNIX-системе), Linux fuser (1) предлагает некоторые дополнительные опции, а также flock (1) .
источник
if ! mkdir
деталь с проверкой, действительно ли процесс с PID, сохраненным (при успешном запуске) внутри lockdir, действительно работает и идентичен сценарию для защиты стандартизации. Это также защитит от повторного использования PID после перезагрузки и даже не потребуетfuser
.mkdir
не определено, чтобы быть атомарной операцией и как таковой, что «побочный эффект» является деталью реализации файловой системы. Я полностью верю ему, если он говорит, что NFS не реализует это атомарно. Хотя я не подозреваю, что у вас/tmp
будет общий ресурс NFS, и он, вероятно, будет предоставлен fs, который реализуетmkdir
атомарно.ln
для создания жесткой ссылки из другого файла. Если у вас есть странные файловые системы, которые не гарантируют этого, вы можете проверить inode нового файла, чтобы узнать, совпадает ли он с исходным файлом.open(... O_CREAT|O_EXCL)
. Вам просто нужна подходящая пользовательская программа, например,lockfile-create
(inlockfile-progs
) илиdotlockfile
(inliblockfile-bin
). И убедитесь, что вы чистите правильно (напримерtrap EXIT
), или проверьте на устаревшие замки (например, с--use-pid
).Вокруг системного вызова flock (2) есть обертка, которая невообразимо называется flock (1). Это позволяет относительно легко получить эксклюзивные блокировки, не беспокоясь об очистке и т. Д. На странице руководства приведены примеры использования этой функции в сценарии оболочки.
источник
flock()
Системный вызов POSIX и не работает для файлов на монтирует NFS.flock -x -n %lock file% -c "%command%"
чтобы убедиться, что выполняется только один экземпляр.Вам нужна атомарная операция, например, flock, иначе это в конечном итоге не удастся.
Но что делать, если стадо недоступно. Ну, есть Mkdir. Это тоже атомарная операция. Только один процесс приведет к успешному выполнению mkdir, все остальные потерпят неудачу.
Итак, код:
Вам нужно позаботиться об устаревших блокировках, иначе после сбоя ваш скрипт никогда не запустится снова.
источник
sleep 10
передrmdir
и попробуйте снова каскадировать - ничто не будет «просачиваться».Чтобы сделать блокировку надежной, вам нужна атомарная операция. Многие из вышеперечисленных предложений не являются атомарными. Предложенная утилита lockfile (1) выглядит многообещающе, поскольку на странице руководства упоминается, что она «устойчива к NFS». Если ваша ОС не поддерживает lockfile (1) и ваше решение должно работать на NFS, у вас не так много вариантов ....
NFSv2 имеет две атомарные операции:
В NFSv3 вызов create также является атомарным.
Операции с каталогами НЕ являются атомарными в NFSv2 и NFSv3 (см. Книгу «Иллюстрированный NFS» Брента Каллагана, ISBN 0-201-32570-5; Брент - ветеран NFS в Sun).
Зная это, вы можете реализовать спин-блокировки для файлов и каталогов (в оболочке, а не в PHP):
заблокировать текущий каталог:
заблокировать файл:
unlock current dir (предположим, что запущенный процесс действительно получил блокировку):
разблокировать файл (предположим, что запущенный процесс действительно получил блокировку):
Удалить также не атомарный, поэтому сначала переименуйте (который является атомным), а затем удалить.
Для вызовов symlink и rename оба имени файла должны находиться в одной файловой системе. Мое предложение: использовать только простые имена файлов (без путей) и поместить файл и заблокировать в том же каталоге.
источник
lockfile
если доступно, или откат к этомуsymlink
методу, если нет.mv
,rm
) следуетrm -f
использовать, а неrm
в случае гонок двух процессов P1, P2? Например, P1 начинает разблокировку сmv
, затем P2 блокирует, затем P2 разблокирует (обаmv
иrm
), в конце концов P1 пытаетсяrm
и терпит неудачу.$$
в${f}.deleteme
имя файла.Другой вариант - использовать
noclobber
опцию оболочки при запускеset -C
. Тогда не>
получится, если файл уже существует.Вкратце:
Это заставляет оболочку вызывать:
который атомарно создает файл или терпит неудачу, если файл уже существует.
Согласно комментарию к BashFAQ 045 , это может не сработать
ksh88
, но оно работает во всех моих оболочках:Интересно, что
pdksh
добавляетO_TRUNC
флаг, но, очевидно, это избыточно:либо вы создаете пустой файл, либо ничего не делаете.
То, как вы это сделаете,
rm
зависит от того, как вы хотите обрабатывать нечистые выходы.Удалить на чистый выход
Новые запуски терпят неудачу, пока проблема, которая вызвала нечистый выход, не будет решена, и файл блокировки удален вручную.
Удалить на любом выходе
Новые запуски успешны, если сценарий еще не запущен.
источник
Вы можете использовать
GNU Parallel
это как мьютекс при вызове какsem
. Итак, в конкретных терминах, вы можете использовать:Если вы тоже хотите установить тайм-аут, используйте:
Тайм-аут <0 означает выход без выполнения скрипта, если семафор не был выпущен в течение тайм-аута, тайм-аут> 0 означает, что скрипт все равно будет запущен.
Обратите внимание, что вы должны дать ему имя (с
--id
), иначе он по умолчанию будет управляющим терминалом.GNU Parallel
это очень простая установка на большинстве платформ Linux / OSX / Unix - это всего лишь скрипт на Perl.источник
sem
смежный вопрос unix.stackexchange.com/a/322200/199525 .Для сценариев оболочки, я , как правило, идут с
mkdir
более ,flock
как это делает замки более переносимым.В любом случае, использования
set -e
недостаточно. Это выходит из сценария только в случае сбоя какой-либо команды. Ваши замки все равно останутся позади.Для правильной очистки блокировки вы действительно должны установить свои ловушки на что-то вроде этого кода psuedo (поднятого, упрощенного и непроверенного, но из активно используемых скриптов):
Вот что будет. Все ловушки будут создавать выход, поэтому функция
__sig_exit
всегда будет выполняться (за исключением SIGKILL), которая очищает ваши блокировки.Примечание: мои значения выхода не являются низкими значениями. Зачем? Различные системы пакетной обработки делают или имеют ожидания от чисел от 0 до 31. Установив их на что-то другое, я могу заставить свои сценарии и потоки пакетов реагировать соответственно на предыдущее пакетное задание или сценарий.
источник
rm -r $LOCK_DIR
или даже принудительно применить ее по мере необходимости (как я это сделал в особых случаях, например, при хранении относительных файлов с нулями). Приветствия.exit 1002
?Действительно быстро и действительно грязно? Эта однострочная строка в верхней части вашего скрипта будет работать:
Конечно, просто убедитесь, что имя вашего скрипта уникально. :)
источник
-gt 2
? grep не всегда оказывается в результате ps!pgrep
не в POSIX. Если вы хотите, чтобы это работало переносимо, вам нужен POSIXps
и обработать его вывод.-c
не существует, вам придется использовать| wc -l
. О сравнении чисел:-gt 1
проверяется, так как первый экземпляр видит себя.Вот подход, который объединяет атомарную блокировку каталогов с проверкой устаревшей блокировки через PID и перезапускает, если устарел. Кроме того, это не зависит от каких-либо нарушений.
источник
Создать файл блокировки в известном месте и проверить его существование при запуске скрипта? Поместить PID в файл может быть полезно, если кто-то пытается отследить ошибочный экземпляр, препятствующий выполнению сценария.
источник
Этот пример объясняется в man flock, но он требует некоторых улучшений, потому что мы должны управлять ошибками и кодами выхода:
Вы можете использовать другой метод, перечислить процессы, которые я использовал в прошлом. Но это сложнее, чем метод выше. Вы должны перечислить процессы по ps, отфильтровать по его имени, дополнительный фильтр grep -v grep для удаления паразита и, наконец, считать его по grep -c. и сравните с номером. Это сложно и неопределенно
источник
Опубликованные существующие ответы либо используют утилиту CLI,
flock
либо неправильно защищают файл блокировки. Утилита flock доступна не во всех системах, отличных от Linux (например, FreeBSD), и не работает должным образом в NFS.В первые годы системного администрирования и разработки системы мне говорили, что безопасным и относительно переносимым способом создания файла блокировки было создание временного файла с использованием
mkemp(3)
илиmkemp(1)
, запись идентифицирующей информации во временный файл (т. Е. PID), затем жесткая ссылка. временный файл к файлу блокировки. Если ссылка была успешной, значит, вы успешно получили блокировку.При использовании блокировок в сценариях оболочки я обычно помещаю
obtain_lock()
функцию в общий профиль и затем извлекаю ее из сценариев. Ниже приведен пример моей функции блокировки:Ниже приведен пример использования функции блокировки:
Не забудьте звонить
clean_up
в любой точке выхода в вашем сценарии.Я использовал вышеупомянутое в Linux и FreeBSD.
источник
Ориентируясь на компьютер Debian, я считаю
lockfile-progs
пакет хорошим решением.procmail
также поставляется сlockfile
инструментом. Однако иногда я застреваю ни с одним из них.Вот мое решение, которое использует
mkdir
атомарность и PID-файл для обнаружения устаревших блокировок. Этот код в настоящее время находится в производстве на установке Cygwin и работает хорошо.Чтобы использовать его просто позвоните,
exclusive_lock_require
когда вам нужно получить эксклюзивный доступ к чему-либо. Необязательный параметр имени блокировки позволяет разделять блокировки между различными сценариями. Также есть две функции более низкого уровня (exclusive_lock_try
иexclusive_lock_retry
), если вам нужно что-то более сложное.источник
Если ограничения стада, которые уже были описаны в этом разделе, не являются для вас проблемой, тогда это должно сработать:
источник
-n
будетexit 1
сразу же , если он не может получить блокировкуУ некоторых юниксов есть
lockfile
что очень похоже на уже упомянутоеflock
.Из справочной страницы:
источник
lockfile
утилиту ??lockfile
распространяется сprocmail
. Также есть альтернатива,dotlockfile
которая идет сliblockfile
пакетом. Они оба утверждают, что надежно работают на NFS.На самом деле, хотя ответ bmdhacks почти хороший, есть небольшой шанс, что второй скрипт запустится после первой проверки файла блокировки и до того, как он его написал. Таким образом, они оба напишут файл блокировки, и они оба будут работать. Вот как заставить это работать наверняка:
noclobber
Вариант будет убедиться , что редирект команда потерпит неудачу , если файл уже существует. Таким образом, команда перенаправления на самом деле атомарна - вы пишете и проверяете файл одной командой. Вам не нужно удалять файл блокировки в конце файла - он будет удален ловушкой. Я надеюсь, что это поможет людям, которые будут читать это позже.PS Я не видел, что Микель уже правильно ответил на вопрос, хотя он не включил команду trap, чтобы уменьшить вероятность того, что файл блокировки останется после остановки сценария, например, с помощью Ctrl-C. Так что это полное решение
источник
Я хотел покончить с lockfiles, lockdirs, специальными программами блокировки и даже
pidof
потому, что он не найден во всех установках Linux. Также хотелось иметь максимально простой код (или, по крайней мере, как можно меньше строк). Простейшееif
утверждение в одну строку:источник
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Я использую простой подход, который обрабатывает устаревшие файлы блокировки.
Обратите внимание, что некоторые из приведенных выше решений, в которых хранится пид, игнорируют тот факт, что пид может обернуться вокруг. Итак, просто проверить, существует ли допустимый процесс с сохраненным pid, недостаточно, особенно для долго выполняющихся сценариев.
Я использую noclobber, чтобы убедиться, что только один скрипт может открывать и записывать в файл блокировки одновременно. Кроме того, я храню достаточно информации, чтобы однозначно идентифицировать процесс в файле блокировки. Я определяю набор данных, чтобы однозначно идентифицировать процесс, который будет pid, ppid, lstart.
Когда запускается новый скрипт, если он не может создать файл блокировки, он затем проверяет, что процесс, который создал файл блокировки, все еще существует. Если нет, мы предполагаем, что первоначальный процесс умер из-за неуместной смерти и оставил файл устаревшей блокировки. Затем новый сценарий становится владельцем файла блокировки, и снова все в порядке.
Должен работать с несколькими оболочками на разных платформах. Быстро, портативно и просто.
источник
Добавьте эту строку в начале вашего скрипта
Это стандартный код от паствы людей.
Если вы хотите больше регистрации, используйте этот
Это устанавливает и проверяет блокировки с помощью
flock
утилиты. Этот код обнаруживает, запускался ли он впервые, проверяя переменную FLOCKER, если для него не задано имя сценария, затем он пытается рекурсивно запустить сценарий снова с использованием flock и с инициализированной переменной FLOCKER, если FLOCKER установлен правильно, а затем выполнить flock на предыдущей итерации. удалось, и это нормально, чтобы продолжить. Если блокировка занята, она терпит неудачу с настраиваемым кодом выхода.Кажется, он не работает на Debian 7, но, похоже, снова работает с экспериментальным пакетом util-linux 2.25. Он пишет "flock: ... Text file busy". Это может быть отменено путем отключения разрешения на запись в вашем скрипте.
источник
PID и lockfiles, безусловно, самые надежные. Когда вы пытаетесь запустить программу, она может проверить файл блокировки, который и, если он существует, он может использовать,
ps
чтобы увидеть, работает ли еще процесс. Если это не так, скрипт может запуститься, обновив PID в файле блокировки до своего собственного.источник
Я считаю, что решение bmdhack является наиболее практичным, по крайней мере, для моего случая использования. Использование flock и lockfile основывается на удалении lockfile с использованием rm, когда скрипт завершается, что не всегда может быть гарантировано (например, kill -9).
Я хотел бы изменить одну незначительную вещь в решении bmdhack: он имеет смысл удалить файл блокировки, не заявляя, что это не нужно для безопасной работы этого семафора. Его использование kill -0 гарантирует, что старый файл блокировки для мертвого процесса будет просто проигнорирован / перезаписан.
Поэтому мое упрощенное решение состоит в том, чтобы просто добавить следующее в начало вашего синглтона:
Конечно, у этого скрипта все еще есть недостаток, заключающийся в том, что процессы, которые могут запускаться одновременно, имеют опасность гонки, поскольку тест блокировки и операции установки не являются единичным атомарным действием. Но предлагаемое решение для этого с помощью lhunath для использования mkdir имеет недостаток, заключающийся в том, что убитый скрипт может оставить каталог, таким образом предотвращая запуск других экземпляров.
источник
В semaphoric утилита использует
flock
(как описано выше, например , с помощью presto8) для осуществления подсчета семафора . Это позволяет любое конкретное количество параллельных процессов, которые вы хотите. Мы используем его для ограничения уровня параллелизма различных рабочих процессов очереди.Это похоже на сем, но гораздо легче. (Полное раскрытие: я написал это после того, как обнаружил, что sem слишком тяжел для наших нужд, и не было простой утилиты для подсчета семафоров.)
источник
Пример с flock (1), но без подоболочки. Файл flock () ed / tmp / foo никогда не удаляется, но это не имеет значения, так как он получает flock () и un-flock () ed.
источник
Ответили уже миллион раз, но по-другому, без необходимости внешних зависимостей:
Каждый раз он записывает текущий PID ($$) в файл блокировки и при запуске скрипта проверяет, запущен ли процесс с последним PID.
источник
Использование блокировки процесса намного сильнее и заботится о неблаговидных выходах. lock_file остается открытым, пока процесс запущен. Он будет закрыт (оболочкой), как только процесс будет существовать (даже если его убьют). Я нашел это очень эффективным:
источник
Я использую oneliner @ в самом начале сценария:
Хорошо видеть присутствие процесса в памяти (независимо от состояния процесса); но это делает работу для меня.
источник
Путь стада - путь. Подумайте о том, что происходит, когда сценарий внезапно умирает. В случае с стадом вы просто теряете стадо, но это не проблема. Кроме того, обратите внимание, что злой трюк состоит в том, чтобы наброситься на сам сценарий ... но это, конечно, позволяет вам полностью справиться с проблемами разрешения.
источник
Быстро и грязно?
источник