Использование именованных каналов с bash-проблемой потери данных
сделал поиск в интернете, нашел простые "учебники" для использования именованных каналов. Однако, когда я что-то делаю с фоновыми заданиями, я, кажется, теряю много данных.
[[Edit: найдено гораздо более простое решение, см. ответ на сообщение. Поэтому вопрос, который я выдвинул, теперь Академический-в случае, если вам может понадобиться сервер работы]]
использование Ubuntu 10.04 с Linux 2.6.32-25-generic #45-Ubuntu SMP Sat 16 октября 19:52: 42 UTC 2010 x86_64 GNU/Linux
GNU bash, версия 4.1.5 (1)-выпуск (x86_64-pc-linux-gnu).
моя функция bash:
function jqs
{
pipe=/tmp/__job_control_manager__
trap "rm -f $pipe; exit" EXIT SIGKILL
if [[ ! -p "$pipe" ]]; then
mkfifo "$pipe"
fi
while true
do
if read txt <"$pipe"
then
echo "$(date +'%Y'): new text is [[$txt]]"
if [[ "$txt" == 'quit' ]]
then
break
fi
fi
done
}
я запускаю это в фоновом режиме:
> jqs&
[1] 5336
и теперь я кормлю его:
for i in 1 2 3 4 5 6 7 8
do
(echo aaa$i > /tmp/__job_control_manager__ && echo success$i &)
done
вывод является противоречивым. Я часто не получаю Эхо успеха. Я получаю максимум столько новых текстовых Эхо, сколько Эхо успеха, иногда меньше.
если я удалю " & " из "ленты", это, кажется, работает, но я заблокирован, пока не будет прочитан вывод. Поэтому я хочу, чтобы подпроцессы получили заблокирован, но не основной процесс.
цель состоит в том, чтобы написать простой скрипт управления заданиями, чтобы я мог запускать, скажем, 10 заданий параллельно, а остальные-для последующей обработки, но надежно знать, что они выполняются.
работа менеджер ниже:
function jq_manage
{
export __gn__=""
pipe=/tmp/__job_control_manager_"$__gn__"__
trap "rm -f $pipe" EXIT
trap "break" SIGKILL
if [[ ! -p "$pipe" ]]; then
mkfifo "$pipe"
fi
while true
do
date
jobs
if (($(jobs | egrep "Running.*echo '%#_Group_#%_$__gn__'" | wc -l) < $__jN__))
then
echo "Waiting for new job"
if read new_job <"$pipe"
then
echo "new job is [[$new_job]]"
if [[ "$new_job" == 'quit' ]]
then
break
fi
echo "In group $__gn__, starting job $new_job"
eval "(echo '%#_Group_#%_$__gn__' > /dev/null; $new_job) &"
fi
else
sleep 3
fi
done
}
function jq
{
# __gn__ = first parameter to this function, the job group name (the pool within which to allocate __jN__ jobs)
# __jN__ = second parameter to this function, the maximum of job numbers to run concurrently
export __gn__=""
shift
export __jN__=""
shift
export __jq__=$(jobs | egrep "Running.*echo '%#_GroupQueue_#%_$__gn__'" | wc -l)
if (($__jq__ '<' 1))
then
eval "(echo '%#_GroupQueue_#%_$__gn__' > /dev/null; jq_manage $__gn__) &"
fi
pipe=/tmp/__job_control_manager_"$__gn__"__
echo $@ >$pipe
}
вызов
jq <name> <max processes> <command>
jq abc 2 sleep 20
начнет процесс. Эта часть отлично работает. Начни второй, хорошо. По одному кажется, работают хорошо. Но запуск 10 в цикле, похоже, теряет систему, как в простой пример выше.
любые намеки на то, что я могу сделать, чтобы решить эту очевидную потерю данных IPC, были бы очень признательны.
С уважением, Алена.
6 ответов
ваша проблема if
заявление ниже:
while true
do
if read txt <"$pipe"
....
done
происходит то, что ваш сервер очереди заданий открывает и закрывает канал каждый раз вокруг цикла. Это означает, что некоторые клиенты получают ошибку "сломанной трубы", когда они пытаются написать в трубу - то есть читатель трубы уходит после того, как писатель открывает его.
чтобы исправить это, измените цикл на сервере, откройте канал один раз для всего цикла:
while true
do
if read txt
....
done < "$pipe"
готово таким образом, труба открывается один раз и остается открытой.
вам нужно будет быть осторожным с тем, что вы запускаете внутри цикла, так как вся обработка внутри цикла будет иметь stdin, прикрепленный к именованному каналу. Вам нужно будет перенаправить stdin всех ваших процессов внутри цикла из другого места, иначе они могут потреблять данные из канала.
Edit: теперь проблема в том, что вы получаете EOF при чтении, когда последний клиент закрывает канал, вы можете используйте метод jilles для обмана дескрипторов файлов, или вы можете просто убедиться, что Вы тоже являетесь клиентом и держите сторону записи открытой:
while true
do
if read txt
....
done < "$pipe" 3> "$pipe"
это будет держать сторону записи трубы открытой на fd 3. Такое же предостережение применяется к этому файловому дескриптору, как и к stdin. Вам нужно будет закрыть его, чтобы любые дочерние процессы не унаследовали его. Это, вероятно, имеет меньшее значение, чем с stdin, но это было бы чище.
как сказано в других ответах, вам нужно держать fifo открытым все время, чтобы избежать потери данных.
однако, как только все писатели ушли после того, как fifo был открыт (так что был писатель), читает возвращение немедленно (и poll()
возвращает POLLHUP
). Единственный способ очистить это состояние-снова открыть fifo.
POSIX не предоставляет решения для этого, но, по крайней мере, Linux и FreeBSD делают: если чтение начинается с сбоя, снова откройте fifo, сохраняя исходный дескриптор открыть. Это работает, потому что в Linux и FreeBSD состояние "зависания" локально для конкретного описания открытого файла, в то время как в POSIX оно глобально для fifo.
Это можно сделать скрипт вроде этого:
while :; do
exec 3<tmp/testfifo
exec 4<&-
while read x; do
echo "input: $x"
done <&3
exec 4<&3
exec 3<&-
done
как camh & Деннис Уильямсон говорят, Не ломайте трубу.
Теперь у меня есть меньшие примеры, прямо в командной строке:
сервер:
(
for i in {0,1,2,3,4}{0,1,2,3,4,5,6,7,8,9};
do
if read s;
then echo ">>$i--$s//";
else
echo "<<$i";
fi;
done < tst-fifo
)&
клиент:
(
for i in {%a,#b}{1,2}{0,1};
do
echo "Test-$i" > tst-fifo;
done
)&
может заменить ключевую строку на:
(echo "Test-$i" > tst-fifo&);
все данные клиента, отправленные в канал, считываются, хотя с опцией два клиента может потребоваться запустить сервер пару раз, прежде чем все данные будут считаны.
но хотя чтение ждет данных в труба для начала, как только данные были перемещены, она читает пустую строку навсегда.
любой способ остановить это?
Спасибо за любые идеи еще раз.
только для тех, кто может быть заинтересован, [[повторно отредактировано]] после комментариев camh и jilles, вот две новые версии сценария тестового сервера.
обе версии теперь работают точно так, как надеялись.
версия camh для управления трубой:
function jqs # Job queue manager
{
pipe=/tmp/__job_control_manager__
trap "rm -f $pipe; exit" EXIT TERM
if [[ ! -p "$pipe" ]]; then
mkfifo "$pipe"
fi
while true
do
if read -u 3 txt
then
echo "$(date +'%Y'): new text is [[$txt]]"
if [[ "$txt" == 'quit' ]]
then
break
else
sleep 1
# process $txt - remember that if this is to be a spawned job, we should close fd 3 and 4 beforehand
fi
fi
done 3< "$pipe" 4> "$pipe" # 4 is just to keep the pipe opened so any real client does not end up causing read to return EOF
}
версия jille для управления трубами:
function jqs # Job queue manager
{
pipe=/tmp/__job_control_manager__
trap "rm -f $pipe; exit" EXIT TERM
if [[ ! -p "$pipe" ]]; then
mkfifo "$pipe"
fi
exec 3< "$pipe"
exec 4<&-
while true
do
if read -u 3 txt
then
echo "$(date +'%Y'): new text is [[$txt]]"
if [[ "$txt" == 'quit' ]]
then
break
else
sleep 1
# process $txt - remember that if this is to be a spawned job, we should close fd 3 and 4 beforehand
fi
else
# Close the pipe and reconnect it so that the next read does not end up returning EOF
exec 4<&3
exec 3<&-
exec 3< "$pipe"
exec 4<&-
fi
done
}
спасибо всем за помощь.
С одной стороны, проблема хуже, чем я думал: Теперь, кажется, есть случай в моем более сложном примере (jq_manage), когда одни и те же данные читаются снова и снова из канала (даже если в него не записываются новые данные).
С другой стороны, я нашел простое решение (редактировать следующий комментарий Денниса):
function jqn # compute the number of jobs running in that group
{
__jqty__=$(jobs | egrep "Running.*echo '%#_Group_#%_$__groupn__'" | wc -l)
}
function jq
{
__groupn__=""; shift # job group name (the pool within which to allocate $__jmax__ jobs)
__jmax__=""; shift # maximum of job numbers to run concurrently
jqn
while (($__jqty__ '>=' $__jmax__))
do
sleep 1
jqn
done
eval "(echo '%#_Group_#%_$__groupn__' > /dev/null; $@) &"
}
работает как шарм. Ни розетки, ни трубы. Простой.
выполнить не более 10 заданий параллельно и поставить остальные в очередь для последующей обработки, но надежно знать, что они выполняются
вы можете сделать это с помощью GNU Parallel. Вам не понадобится этот сценарий.
http://www.gnu.org/software/parallel/man.html#options
вы можете установить max-procs " количество рабочих мест. Запуск до N заданий параллельно."Есть вариант установить количество ядер процессора, которые вы хотите использовать. Вы можете сохранить список выполненных заданий в файл журнала, но это бета-функция.