Иногда необходимо убедиться, что одновременно запущен только один экземпляр сценария оболочки.
Например, задание cron, которое выполняется через crond, которое не обеспечивает самостоятельную блокировку (например, crond по умолчанию Solaris).
Распространенным шаблоном для реализации блокировки является такой код:
#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then # 'test' -> race begin
echo Job is already running\!
exit 6
fi
touch $LOCK # 'set' -> race end
# do some work
rm $LOCK
Конечно, у такого кода есть условие гонки. Существует временное окно, в котором выполнение двух экземпляров может продвинуться после строки 3, прежде чем один сможет прикоснуться к $LOCK
файлу.
Для задания cron это обычно не является проблемой, потому что у вас есть интервал минут между двумя вызовами.
Но что-то может пойти не так - например, когда файл блокировки находится на сервере NFS - это зависает. В этом случае несколько заданий cron могут блокироваться в строке 3 и помещаться в очередь. Если сервер NFS снова активен, то у вас есть огромное количество параллельно выполняемых заданий.
Выполняя поиск в Интернете, я обнаружил инструмент lockrun, который кажется хорошим решением этой проблемы. С его помощью вы запускаете скрипт, который нуждается в блокировке следующим образом:
$ lockrun --lockfile=/var/tmp/mylock myscript.sh
Вы можете положить это в обертку или использовать его из вашего crontab.
Он использует lockf()
(POSIX), если доступно, и использует flock()
(BSD). И lockf()
поддержка по NFS должна быть относительно широко распространена.
Есть ли альтернативы lockrun
?
А как насчет других демонов cron? Существуют ли распространенные crond, которые поддерживают блокировку в разумных пределах? Быстрый просмотр справочной страницы Vixie Crond (по умолчанию в системах Debian / Ubuntu) ничего не показывает о блокировке.
Было бы хорошей идеей включить такой инструмент, как lockrun
в coreutils ?
На мой взгляд , он реализует тему , очень похожий на timeout
, nice
и друзей.
kill
; и кажется хорошей практикой хранить собственный файл в файле замка, а не просто прикасаться к нему.Ответы:
Вот еще один способ сделать блокировку в сценарии оболочки, которая может предотвратить состояние гонки, которое вы описали выше, когда два задания могут оба пройти строку 3.
noclobber
Опция будет работать в ksh и bash. Не используйте,set noclobber
потому что вы не должны писать сценарии в csh / tcsh. ;)YMMV с блокировкой на NFS (вы знаете, когда NFS-серверы недоступны), но в целом он гораздо надежнее, чем раньше. (10 лет назад)
Если у вас есть задания cron, которые выполняют одно и то же одновременно с нескольких серверов, но вам нужен только один экземпляр, чтобы запустить его, то подобное может сработать для вас.
У меня нет опыта работы с lockrun, но может помочь предустановленная среда блокировки до запуска сценария. Или это не так. Вы просто устанавливаете тест для файла блокировки вне своего скрипта в обертке, и теоретически, вы не можете просто выполнить одно и то же состояние гонки, если две команды были вызваны lockrun в одно и то же время, точно так же, как и с «inside-» решение сценария?
В любом случае блокировка файлов в значительной степени учитывает поведение системы, и любые сценарии, которые не проверяют существование файла блокировки перед запуском, будут делать то, что они собираются делать. Просто введя тест файла блокировки и правильное поведение, вы решите 99% потенциальных проблем, если не 100%.
Если вы часто сталкиваетесь с условиями гонки в lockfile, это может быть индикатором более серьезной проблемы, например, неправильного определения времени ваших заданий или, возможно, если интервал не так важен, как завершение задания, возможно, ваша работа лучше подходит для того, чтобы ее демонизировать ,
РЕДАКТИРОВАТЬ НИЖЕ - 2016-05-06 (если вы используете KSH88)
Основываясь на комментарии @Clint Pachl ниже, если вы используете ksh88, используйте
mkdir
вместоnoclobber
. Это в основном смягчает потенциальное состояние гонки, но не ограничивает его полностью (хотя риск минимален). Для получения дополнительной информации прочитайте ссылку, которую Клинт разместил ниже .И, как дополнительное преимущество, если вам нужно создать tmpfiles в вашем скрипте, вы можете использовать
lockdir
каталог для них, зная, что они будут очищены при выходе из скрипта.Для более современного bash, метод noclobber наверху должен быть подходящим.
источник
lockf()
системном вызове - когда он резервируется, все процессы возобновляются, но только один процесс блокирует блокировку. Нет расы. Я не сталкиваюсь с такими проблемами с cronjobs много раз - дело обстоит наоборот - но это проблема, когда она бьет вас, она может причинить много боли.set -o noclobber && echo "$$" > "$lockfile"
получить безопасный запасной вариант, когда оболочка не поддерживает опцию noclobber.noclobber
Вариант может быть склонным к условиям гонки. См. Mywiki.wooledge.org/BashFAQ/045, чтобы найти пищу для размышлений.noclobber
(или-C
) в ksh88 не работает, потому что ksh88 не используетO_EXCL
дляnoclobber
. Если вы работаете с более новой оболочкой, вы можете быть в порядке ...Я предпочитаю использовать жесткие ссылки.
Жесткие ссылки атомарны по NFS, и по большей части, mkdir также . Использование
mkdir(2)
илиlink(2)
примерно то же самое, на практическом уровне; Я просто предпочитаю использовать жесткие ссылки, потому что больше реализаций NFS допускают атомарные жесткие ссылки, чем атомарныеmkdir
. С современными выпусками NFS вам не нужно беспокоиться об их использовании.источник
Я понимаю, что
mkdir
это атомное, так что, возможно,источник
mkdir()
NFS (> = 3) атомарное.mkdir
к атомарности (это делает дляrename
). На практике известно, что некоторые реализации не являются. Связанный: интересная тема, в том числе вклад автора GNU Arch .Самый простой способ - использовать
lockfile
приходящий обычно сprocmail
пакетом.источник
sem
который входит в составparallel
инструментов GNU , может быть тем, что вы ищете:Как в:
Вывод:
Обратите внимание, что порядок не гарантируется. Также вывод не отображается, пока не закончится (раздражает!). Но даже в этом случае я знаю, что это самый лаконичный способ защиты от одновременного выполнения, не беспокоясь о файлах блокировки, повторных попытках и очистке.
источник
sem
ручкой, в середине исполнения?Я использую
dtach
.источник
Я использую инструмент командной строки «flock» для управления блокировками в моих скриптах bash, как описано здесь и здесь . Я использовал этот простой метод из справочной страницы flock, чтобы запустить некоторые команды в подоболочке ...
В этом примере происходит сбой с кодом выхода 1, если он не может получить файл блокировки. Но flock также можно использовать способами, которые не требуют запуска команд в под-оболочке :-)
источник
flock()
Системный вызов не работает через NFS .flock()
и, таким образом, проблематично по NFS. Между тем, flock () в Linux теперь возвращается к тому моменту,fcntl()
когда файл находится на монтировании NFS, таким образом, в среде NFS только для Linuxflock()
теперь работает поверх NFS.Не используйте файл.
Если ваш скрипт выполняется так, например:
Вы можете определить, работает ли он, используя:
источник
my_script
? В случае, если другой экземпляр работает - неrunning_proc
содержит две совпадающие строки? Мне нравится идея, но, конечно, - вы получите ложные результаты, когда другой пользователь запускает скрипт с таким же именем ...$!
вместо$$
своего примера.grep -v $$
). В основном я пытался предложить другой подход к проблеме.Для фактического использования, вы должны использовать верхний проголосовавший ответ .
Тем не менее, я хочу обсудить несколько различных неработающих и полуработоспособных подходов
ps
и их предостережения, поскольку я продолжаю видеть, как люди их используют.Этот ответ действительно является ответом «Почему бы не использовать
ps
иgrep
обрабатывать блокировку в оболочке?Сломанный подход № 1
Во-первых, подход, приведенный в другом ответе, который имеет несколько положительных отзывов, несмотря на тот факт, что он не работает (и не мог) и явно никогда не проверялся:
Давайте исправим синтаксические ошибки и неверные
ps
аргументы и получим:Этот скрипт всегда будет выходить из 6 каждый раз, независимо от того, как вы его запустите.
Если вы запустите его
./myscript
, тоps
будет просто вывод12345 -bash
, который не соответствует требуемой строке12345 bash ./myscript
, поэтому произойдет сбой.Если вы запустите его
bash myscript
, все станет интереснее. Процесс bash разветвляет запуск конвейера, а дочерняя оболочка запускаетps
иgrep
. И исходная оболочка, и дочерняя оболочка будут отображаться вps
выводе, что-то вроде этого:Это не ожидаемый результат
$$ bash $0
, поэтому ваш скрипт завершится.Неправильный подход № 2
Теперь, честно говоря, для пользователя, который написал неправильный подход № 1, я сам сделал нечто подобное, когда впервые попробовал это:
Это почти работает. Но сам факт разветвления, чтобы запустить трубу, отбрасывает это. Так что и этот всегда будет выходить.
Ненадежный подход № 3
Эта версия устраняет проблему разветвления конвейера в подходе # 2, сначала получая все PID, которые имеют текущий сценарий в своих аргументах командной строки, а затем отфильтровывая этот пидлист отдельно, чтобы пропустить PID текущего сценария.
Это может сработать ... при условии, что ни у какого другого процесса нет командной строки, совпадающей с
$0
, и при условии, что скрипт всегда вызывается одинаково (например, если он вызывается с относительным путем, а затем с абсолютным путем, последний экземпляр не заметит первый ).Ненадежный подход № 4
Так что, если мы пропустим проверку полной командной строки, так как это может не указывать на фактическую работу скрипта, и
lsof
вместо этого проверим, чтобы найти все процессы, у которых этот скрипт открыт?Ну да, такой подход на самом деле не так уж и плох
Конечно, если запущена копия скрипта, то новый экземпляр запустится просто отлично, и у вас будет две запущенные копии .
Или, если работающий скрипт изменен (например, с помощью Vim или с помощью a
git checkout
), тогда «новая» версия скрипта запустится без проблем, так как и Vim, иgit checkout
результатом будет новый файл (новый inode) вместо Старый.Однако, если скрипт никогда не изменяется и никогда не копируется, то эта версия довольно хороша. Условия гонки отсутствуют, потому что файл сценария должен быть открыт до того, как будет достигнута проверка.
Все еще могут быть ложные срабатывания, если у другого процесса открыт файл скрипта, но учтите, что даже если он открыт для редактирования в Vim, vim на самом деле не держит файл скрипта открытым, поэтому не приведет к ложным срабатываниям.
Но помните, не используйте этот подход, если скрипт может быть отредактирован или скопирован, так как вы получите ложные негативы, т.е. несколько экземпляров, запущенных одновременно - поэтому тот факт, что редактирование с помощью Vim не дает ложных срабатываний, не должен иметь значения тебе. Я упоминаю это, хотя, потому что подход # 3 делает ложные срабатывания (т.е. не запускается) , если у вас есть сценарий открытого с Vim.
Так что же делать тогда?
Топ проголосовали ответ на этот вопрос дает хороший твердый подход.
Возможно, вы можете написать лучшую ... но если вы не понимаете всех проблем и предостережений со всеми вышеупомянутыми подходами, вы вряд ли напишете метод блокировки, который позволит избежать их всех.
источник
С помощью инструмента FLOM (Free LOck Manager) сериализация команд становится такой же простой, как и запуск
FLOM позволяет вам реализовать более изощренные варианты использования (распределенная блокировка, считыватели / записи, числовые ресурсы и т. Д.), Как описано здесь: http://sourceforge.net/p/flom/wiki/FLOM%20by%20examples/
источник
Вот кое-что, что я иногда добавляю на сервер, чтобы легко обрабатывать условия гонки для любой работы на машине. Это похоже на пост Тима Кеннеди, но таким образом вы получаете обработку гонки, добавляя только одну строку в каждый скрипт bash, который в этом нуждается.
Поместите содержимое ниже, например, в / opt / racechecker / racechecker:
Вот как это использовать. Обратите внимание на строку после Шебанга:
Он работает так, что вычисляет имя основного файла bashscript и создает pid-файл в «/ tmp». Это также добавляет слушателя к сигналу финиша. Слушатель удалит pid-файл, когда основной скрипт завершит работу.
Вместо этого, если pid-файл существует при запуске экземпляра, будет выполняться оператор if, содержащий код внутри второго оператора if. В этом случае я решил запустить тревожную почту, когда это произойдет.
Что делать, если скрипт вылетает
Еще одним упражнением будет устранение сбоев. В идеале pid-файл должен быть удален, даже если основной скрипт по какой-либо причине дает сбой, это не сделано в моей версии выше. Это означает, что в случае сбоя сценария pid-файл должен быть удален вручную для восстановления функциональности.
В случае сбоя системы
Хорошей идеей будет сохранить pidfile / lockfile, например, в / tmp. Таким образом, ваши сценарии будут обязательно выполняться после сбоя системы, поскольку pid-файлы всегда будут удаляться при загрузке.
источник
Проверьте мой сценарий ...
Вы можете любить это ....
источник
Я предлагаю следующее решение в сценарии с именем «flocktest»
источник