1

У меня есть несколько команд, которые я хотел бы выполнять параллельно, но только если нет конфликтов в ресурсах, к которым они получают доступ. Поэтому я решил использовать 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> &

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

2 ответа2

1

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

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

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) .)

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

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

1

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

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

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


Обходной путь может заключаться в том, чтобы не допустить, чтобы 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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .