Вопрос или проблема
Я протестировал следующее как в bash, так и в dash, и, похоже, это постоянно зависает:
[ -p pipe ] || mkfifo pipe
i=0
while [ $i -lt 10 ]; do
<pipe cat &
: $(( i+=1 ))
done
# sleep 1
echo hello world >pipe
wait
Если мы уменьшаем лимит с 10 до 1, программа не зависает (по крайней мере, не надежно). Ожидание предотвращает зависание.
Я думаю, что команда echo выполняется до того, как все фоновые процессы откроют канал, поэтому в канал записывается EOF, закрывающий канал и завершающий некоторые фоновые процессы. Поздние подключения застревают в ожидании EOF, который так и не приходит.
Это ожидаемое поведение? Мне кажется, это ошибка, но я не уверен, является ли это моей ошибкой или ошибкой оболочки.
Также: я знаю, что могу просто написать echo x | { <вставьте команды здесь> }
, и это было бы аккуратнее, но я обхожу некоторое поведение dash, которое я уже сообщил как ошибку.
Ответ или решение
Должны ли фоновіе задачи вызывать состояние гонки?
Когда речь идет о запуске фоновых задач в оболочке Unix, важно понимание потенциальных состояний гонки, которые могут возникнуть в результате синхронизации процессов. Рассмотренный вами сценарий демонстрирует такой случай.
Описание проблемы
Запуск фонових процессов с использованием FIFO (именованного канала) в вашем коде действительно может привести к подвисанию из-за гонки при доступе к каналу. В вашем примере:
[ -p pipe ] || mkfifo pipe
i=0
while [ $i -lt 10 ]; do
<pipe cat &
: $(( i+=1 ))
done
echo hello world >pipe
wait
Фоновый процесс cat <pipe
должен ждать, пока что-то будет записано в FIFO, чтобы начать свою работу. Однако, есть вероятность, что echo hello world >pipe
выполнится прежде, чем все фоновые процессы успеют открыть канал, что вызовет закрытие канала (EOF) для фоновых процессов, которые еще не успели начать чтение. В результате, некоторые из них могут зависнуть, ожидая данные, которых уже не будет.
Ожидаемое поведение или ошибка?
Это поведение можно считать сочетанием ожидаемого поведения и недостатка синхронизации. Оно происходит из-за неявной предпосылки, что все фоновые процессы успеют открыть файл до записи данных в него. Запись в канал без должной синхронизации создает условия для возможной гонки.
При уменьшении числа фонових процессов с 10 до 1, ситуация стабилизируется, поскольку вероятность того, что запись произойдет до момента открытия канала фоновой задачей, существенно снижается. Добавление команды sleep
также помогает, так как она добавляет задержку, позволяя фоновым процессам открывать канал.
Рекомендации
-
Синхронизация стартовых процессов: Используйте механизмы синхронизации, чтобы убедиться, что все фоновые процессы успели открыть FIFO перед тем, как запись в него будет выполнена.
-
Использование других методов: Возможно рассмотреть альтернативные методы передачи данных (например, использование временных файлов или других IPC механизмов), которые были бы менее подвержены состояниям гонки.
-
Управление выводом: Вместо непосредственной записи в именованный канал, рассмотрите возможность использования группы процессов, как вы и упомянули в следующей строке:
echo x | { <launch commands here> }
.
Заключение
Хоть это и не является стандартным «допустимым» поведением, тот факт, что системы могут зависать из-за гонки при запуске фоновых задач, подчеркивает необходимость должной синхронизации при работе с потоками данных. Это поведение не является исключительно вашей ошибкой или ошибкой оболочки, а указывает на то, что проектирование таких систем требует внимательного подхода. Исходя из этого, важной задачей остается тестирование и отладка сценариев, чтобы предотвратить возникновение подобной неопределенности в будущем.