атомарное удаление первой строки файла и ее возврат

#bash #file #locking #atomic

#bash #файл #блокировка #атомарный

Вопрос:

Я хочу использовать текстовый файл в качестве очереди задач для моего приложения (для записи в базу данных, хотя это не имеет значения), где:

  1. Я добавляю элемент в очередь через echo "some task" >> task_queue.txt
  2. Я удаляю элемент из очереди атомарно

Для 2. Я не знаю способа избежать условий гонки, если я обращаюсь / изменяю task_queue.txt в нескольких потоках или процессах. Следующее не является атомарным:

 ITEM=`head -1 task_queue.txt`
sed -i '1d' task_queue.txt
# process the item in the application
 

Предоставляет ли Bash более элегантный способ сделать это, чем использование файла блокировки? Я никогда flock раньше не использовал, поэтому я не знаю, беспорядочно ли это (например, когда обработка задач моего приложения завершается с ошибкой).

Ответ №1:

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


Одним из способов может быть использование pgrep -f опции для сопоставления с полной командной строкой и сопоставления, если файл соответствует, т.Е. Если какой-либо процесс обращается к этому файлу. Это предполагает, что процесс не изменяет свою командную строку.

Это может сделать:

 if ! pgrep -f task_queue.txt amp;>/dev/null; then
    ## File not Open, do stuff
else
    ## File is Open, do stuff
fi
 

Другой подход будет включать синтаксический lsof анализ или fuser (это то же самое, что и синтаксический /proc/PID/fd/* анализ):

 if ! lsof /path/to/task_queue.txt amp;>/dev/null; then
    ## File not Open, do stuff
else
    ## File is Open, do stuff
fi
 

Аналогично fuser :

 if ! fuser /path/to/task_queue.txt amp;>/dev/null; then
    ## File not Open, do stuff
else
    ## File is Open, do stuff
fi
 

Обратите внимание, что здесь мы отправляем как STDOUT, так и STDERR из lsof / fuser to /dev/null , это может быть не всегда желательно, так как может быть какое-то предупреждение / ошибка, и поскольку мы зависим только от статуса выхода, все они будут неправильно обрабатываться при использовании файла. Это было бы проще реализовать, если lsof / fuser имеет разный статус выхода для разных событий, но все, что я вижу, — это 1 для каждого вида сбоя или отсутствия совпадения.

Комментарии:

1. Интересный подход, о котором я не знал pgrep -f . Интересно, лучше ли это, чем использовать flock. Я предполагаю, что мне придется использовать оба варианта одновременно и посмотреть, в каком из них я чувствую себя более уверенно.

2. Хммм, на самом деле, разве не возможно, что «материал» в if теле не удерживается в файловом дескрипторе все время? 2 команды могут иметь небольшой промежуток времени, между которыми файл не открыт, что может позволить другому процессу украсть его.

3. @Sridhar-Sarnobat Очевидно, что существует вероятность состояния гонки между pgrep и операцией с файлом, какой бы маленькой она ни была. Если вам нужна точность, вам следует посмотреть на механизм блокировки, например flock .

4. Да, я думаю. Хотя после pgrep — это не самая большая моя забота. Большее беспокойство вызывают мои многочисленные команды, которые следуют за этим. Но я ценю ваши предложения и примеры кода.

5. @Sridhar-Sarnobat Тогда вы можете (возможно) использовать while конструкцию и запускать одну команду за раз (если это возможно).

Ответ №2:

Хотя я не могу подтвердить, что это атомарно, это намного более атомарно, чем все, что я могу написать сам (вероятно), и достаточно хорошо для моего приложения, не являющегося критически важным:

 sed -i -e '1 w /dev/stdout' -e '1d' task_queue.txt
 

(кредиты: https://unix.stackexchange.com/a/108479/7000 )