Команда flock GNU / Linux: как атомарно взять несколько блокировок

715
Renaud Pacalet

У меня есть несколько команд, которые я хотел бы выполнять параллельно, но только если нет конфликтов в ресурсах, к которым они получают доступ. Поэтому я решил использовать flock. Каждая команда должна иметь одну исключительную (запись) блокировку и несколько общих (чтение) блокировок. Так flockкак не поддерживает множественные блокировки, я наивно думал, что-то вроде

flock -x a flock -s b flock -s c ... <command> 

должно сработать. Я быстро обнаружил, что при таком подходе существуют условия гонки, поскольку набор блокировок не является атомарным. При запуске:

flock -x a flock -s b <command1> & flock -x b flock -s a <command2> & 

может случиться так, что две эксклюзивные блокировки будут приняты одновременно, и две команды войдут в тупик, потому что они не могут взять свои общие блокировки.

Есть ли обходной путь? Являются ли они другими утилитами блокировки, которые поддерживают множественные блокировки атомарным способом? Или я должен создать свой собственный, который пытается захватить блокировки, отпустить их все по истечении времени ожидания, если он выходит из строя, и повторить попытку после случайной задержки? Или что-то подобное?

Обновление видимо, сортировка блокировок по имени решает проблему:

flock -x a flock -s b <command1> & flock -s a flock -x b <command2> & 

но насколько это надежно? Будет ли он избегать взаимоблокировок во всех ситуациях с любым количеством команд, количеством блокировок, имен блокировок и наборов блокировок на команду (все еще с ограничением, что для каждой команды существует только одна исключительная блокировка)?

1
Это выглядит мне`и``из ваших примеров никогда не будет работать параллельно. Совместная блокировка предотвратит монопольную блокировку того же файла; эксклюзивная блокировка предотвратит любую блокировку того же файла. Таким образом, вы можете просто запустить эти команды в последовательности. Удостоверьтесь, что ваша настоящая проблема не имеет недостатков таким же образом, иначе вся «параллельная» идея бессмысленна. Если для каждой команды существует только одна исключительная блокировка, то ``может работать параллельно с``если` A` вообще не блокирует эксклюзивный файл `B`, а` B` вообще не блокирует эксклюзивный файл `A`. Kamil Maciorowski 6 лет назад 0
@KamilMaciorowski Я не уверен, что понимаю ваш первый комментарий. Я знаю, что в моих примерах команды, которые я показываю, не будут работать параллельно. В первом примере, потому что есть тупик, а во втором, потому что переупорядочивание блокировок предотвратило тупик, вызывая последовательное выполнение. Они были просто примерами случаев, когда что-то пошло не так. Я что-то пропустил? Renaud Pacalet 6 лет назад 0
Я просто хотел убедиться, что вы знаете, что примеры никогда не выигрывают от параллельного выполнения. Если реальная проблема была аналогичной ошибкой (каждая команда блокирует * каждый * важный файл), вы можете запустить все по порядку и (возможно) без `flock`. Kamil Maciorowski 6 лет назад 0

2 ответа на вопрос

1
Kamil Maciorowski

Это проблема столовых философов . Сортируя блокировки, вы реализуете решение по иерархии ресурсов .

Хотя решение по иерархии ресурсов позволяет избежать взаимоблокировок, оно не всегда практично, особенно когда список необходимых ресурсов заранее не известен полностью.

Похоже, что это надежно, если только вы можете сортировать свои ресурсы и придерживаться его.


Обходной путь может заключаться в том, чтобы не flockждать неопределенно долго, а затем добавить некоторую логику для обнаружения случаев, когда он вышел из-за невозможности заблокировать файл и, например, повторить всю задачу через некоторое случайное время.

В man flockодном можно увидеть:

-n, --nb, --nonblock
Нормально (с выходными кодом 1), а не ждать, если блокировка не может быть немедленно приобретена.

-w, --wait, --timeout seconds
Нормально (с выходными кодом 1), если блокировка не может быть приобретена в течение нескольких секунд секунд. Десятичные дробные значения допускаются.

Проблема в том, что возможный код выхода 1может исходить от любой flockили от базовой команды. Если вы flockподдерживаете -Eуказание пользовательского кода выхода - используйте его, возможно.

Это простой пример подхода:

while ! flock -n -x file <command> ; do sleep $(($RANDOM%5)) ; done 

Вы можете использовать несколько flock-s. Если какой-либо из них не может заблокировать файл, все блокировки снимаются, и вся строка ожидает в sleep, а не в flock; в это время он не будет блокировать другую аналогичную линию, выполняемую параллельно.

Да, спасибо, это решение, которое я предлагал в своем вопросе. Но я ожидал чего-то лучшего, если оно существует. Renaud Pacalet 6 лет назад 0
Вы правы, Камил, это похоже на решение иерархии ресурсов проблемы столовых философов ... За исключением общих блокировок и уникальных эксклюзивных блокировок на задание. Что я хотел бы знать, так это что-то меняет или нет. Renaud Pacalet 6 лет назад 0
1
AFH

Когда я принимал участие в программировании в реальном времени, я всегда ненавидел решения с задержкой / повторной попыткой, хотя их часто проще кодировать.

Ключ к избежанию тупика - никогда не ставить в очередь на вторую блокировку, удерживая блокировку. Итак, для трех файлов используйте что-то вроде:

while true do flock -x a flock -nE 101 -s b flock -nE 102 c Command case $? in 101) flock -s b;; 102) flock -s c;; *) break;; done 

Используемые возвращаемые значения flock -Eдолжны быть значениями, которые никогда не возвращаются командой, и когда возвращается одно из этих значений, сценарий ставится в очередь для заблокированного ресурса, а затем повторяет исходный вызов.

В принципе, не имеет значения, в каком порядке запрашиваются блокировки, но это может упростить кодирование, чтобы сначала запросить исключительную блокировку.

Существует более эффективное решение, позволяющее избежать освобождения блокировки из очереди непосредственно перед повторным запросом: каждый раз собирайте строку выполнения, перестраивая ее при каждом неблокирующем сбое, например, для возврата 101 строка выполнения становится:

flock -s b flock -nE 102 flock -nE 100 -x a c Command 

(Очевидно, 100)потребуется дополнительный случай для .)

В более общем случае файлы блокировки будут переданы в функцию, которая сохраняет файлы в массиве и строит строку выполнения (последовательность flocks) и использует арифметику параметров, чтобы выбрать файл для постановки в очередь, когда неблокирующая блокировка потерпеть поражение.

Кодирование для них обоих будет сложным, особенно если учесть встроенные пробелы Commandи их параметры, поэтому я выбрал простой случай выше, чтобы проиллюстрировать принцип, который будет потерян в расширенном кодировании для общего случая.

Применительно к посетителям алгоритм заключается в том, чтобы поднять левую вилку, если она доступна; в противном случае, подождите. Если правильная вилка также доступна, возьмите ее и ешьте; если нет, снова положите левую вилку и дождитесь правой. С посетителями может быть состояние гонки, когда каждый поднимает левую вилку в то же самое время. В вычислениях это не может произойти, поскольку в одно мгновение может происходить только одно (это не относится к многокомпьютерной сети, но верно для многоядерного ЦП, если фоновые задачи явно не назначены разным ядрам) , AFH 6 лет назад 0

Похожие вопросы