Создание одного выходного потока из трех других потоков, создаваемых параллельно

10

У меня есть три вида данных в разных форматах; для каждого типа данных существует скрипт Python, который преобразует его в единый унифицированный формат.

Этот сценарий Python является медленным и привязанным к процессору (к одному ядру на многоядерном компьютере), поэтому я хочу запустить три его экземпляра - по одному для каждого типа данных - и объединить их вывод для передачи в него sort. В основном эквивалентно этому:

{ ./handle_1.py; ./handle_2.py; ./handle_3.py } | sort -n

Но три сценария работают параллельно.

Я нашел этот вопрос, где GNU splitиспользовался для циклического перебора некоторого потока stdout между n экземплярами скрипта, который обрабатывает поток.

Из разделенной страницы руководства:

-n, --number=CHUNKS
          generate CHUNKS output files.  See below
CHUNKS  may be:
 N       split into N files based on size of input
 K/N     output Kth of N to stdout
 l/N     split into N files without splitting lines
 l/K/N   output Kth of N to stdout without splitting lines
 r/N     like 'l'  but  use  round  robin  distributio

Таким образом, r/Nкоманда подразумевает « без разделения строк ».

Исходя из этого, представляется целесообразным следующее решение:

split -n r/3 -u --filter="./choose_script" << EOF
> 1
> 2
> 3
> EOF

Где choose_scriptэто:

#!/bin/bash
{ read x; ./handle_$x.py; }

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

Например, если я заменю свои скрипты Python на несколько простых скриптов bash, которые делают это:

#!/bin/bash
# ./handle_1.sh
while true; echo "1-$RANDOM"; done;

,

#!/bin/bash
# ./handle_2.sh
while true; echo "2-$RANDOM"; done;

,

#!/bin/bash
# ./handle_3.sh
while true; echo "3-$RANDOM"; done;

Я вижу этот вывод:

1-8394

2-11238
2-22757
1-723
2-6669
3-3690
2-892
2-312511-24152
2-9317
3-5981

Это раздражает - основываясь на извлечении man-страницы, которое я вставил выше, оно должно поддерживать целостность строки.

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

Если у кого-то есть понимание, это будет с благодарностью. Я здесь из моей глубины.

Cera
источник
Некоторые люди в #bash на freenode предложили мне запустить все три процесса и создать их фон, записать их в пользовательские FD, затем циклически обработать эти FD и прочитать их строки, но я не понял, как сделать это работоспособным. Мне также сказали посмотреть на coprocвстроенную в bash, хотя я не очень понимаю, как это применимо.
Cera
1
Вы должны сделать это без промежуточных файлов? Не могли бы вы просто сделать job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
Ангус

Ответы:

2

Попробуйте использовать параметр -u в GNU параллельно.

echo "1\n2\n3" | parallel -u -IX ./handle_X.sh

Это запускает их параллельно, без буферизации всего процесса.

flowblok
источник
Я немного запутался , - это Xв IXговорить , -Iчто X будет флаг для замены, или его применения -Xфлаг, который , казалось бы , также имеет соответствующее значение?
Cera
Хммм. Я делаю это: parallel -u -X ./handle_{}.sh ::: "1" "2" "3"и, к сожалению, я все еще вижу некоторые искажения вывода.
Cera
первое: вы также можете использовать parallel -u ./handle_{}.sh, но я предпочитаю изменить его, поскольку скобки также имеют смысл объединения команд (как в вашем вопросе).
Flowblok
Кажется, работает для меня, мой grep не улавливает никаких искажений: pastie.org/5113187 (вы используете тестовые сценарии bash или ваши настоящие сценарии Python?)
flowblok
Проблема в том, что он на самом деле ничего не делает параллельно. Я использую скрипты bash - pastie.org/5113225
Cera
2

Пытаться:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py

Если handle_1.pyберет имя файла:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Вы не хотите смешивать вывод, поэтому не используйте -u.

Если вы хотите сохранить порядок (поэтому все выходные данные handle_1 предшествуют handle_2 и, таким образом, вы можете избежать сортировки):

parallel -k  ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Если вы все еще хотите отсортировать, вы можете распараллелить сортировку и использовать sort -m:

parallel --files "./handle_{1}.py {2} | sort -n"  ::: 1 2 3 ::: files* | parallel -j1 -X sort -m

Установите $ TMPDIR в каталог, достаточно большой для хранения вывода.

Оле Танге
источник
1
Я хочу, чтобы вывод был «смешанным» - я просто хочу убедиться, что каждая строка в конечном выводе представляет собой одну строку из одного из подпроцессов. Если я не смешаю это, системе не хватит памяти для буферизации потоков stdout, которые еще не распечатываются.
Cera
С GNU Parallel вы не исчерпаете память: она не буферизуется в памяти. Почему вы думаете, что это буферизируется в памяти?
Оле Танге
2

Может быть, я что-то упустил, но вы не можете просто сделать:

(./handle_1.py & ./handle_2.py & ./handle_3.py) | sort -n

Если вы хотите, чтобы строки каждого процесса не чередовались, возможно, проще будет убедиться, что процесс сам запишет их полностью и, возможно, отключит буферизацию вывода, поскольку writes в канал гарантированно будут атомарными, если они не больше, чем PIPE_BUF, Например, вы можете убедиться , что он делает использование буферизации ля stdioи вызова fflushили любой другой эквивалент в pythonпосле того, как один или нескольких строк были написаны.

Если вы не можете изменить скрипты Python, вы можете сделать:

lb() { grep --line-buffered '^'; }

(с GNU grep) или:

lb() while IFS= read -r l; do printf '%s\n' "$l"; done

(См. Примечания в комментариях ниже, если вывод команды не является текстом)

И делать:

(./handle_1.py | lb & ./handle_2.py | lb & ./handle_3.py | lb) | sort -n

Другой вариант, позволяющий избежать этих трех lbпроцессов, состоит в том, чтобы иметь три канала для одной команды, которая использует select/, pollчтобы увидеть, откуда поступает какой-то вывод, и передать его на основе sortстрок, но это требует небольшого программирования.

Стефан Шазелас
источник
Тебе нужно waitтам, я думаю.
Дероберт
1
Нет, если только некоторые программы не закрывают свой стандартный вывод перед выходом, потому что канал и sort -nбудет оставаться до тех пор, пока все программы, у которых открыт fd, не завершены.
Стефан Шазелас
Действительно, я проверял, вы правы.
Дероберт
Нет, я все еще получаю искаженный вывод. Линии смешиваются и чередуются.
Cera
1
ОК @Cerales, смотрите мой обновленный ответ
Стефан Шазелас
1

Ответ Flowbok был правильным решением. Странно, но вывод GNU parallelискажается, если он выводится непосредственно в файл, но не если он идет в tty.

К счастью, script -cимеется возможность имитировать tty.

Есть еще три сценария:

#!/bin/bash
# handle_1.sh
while true; do echo "1-$RANDOM$RANDOM$RANDOM$RANDOM"; done

,

#!/bin/bash
# handle_2.sh
while true; do echo "2-$RANDOM$RANDOM$RANDOM$RANDOM"; done

,

#!/bin/bash
# handle_3.sh
while true; do echo "3-$RANDOM$RANDOM$RANDOM$RANDOM"; done

Тогда есть файл, который инкапсулирует вызов параллели:

#!/bin/bash
# run_parallel.sh
parallel -u -I N ./handle_N.sh ::: "1" "2" "3"

И тогда я называю это так:

script -c ./run_parallel > output

Строки в выходных данных смешиваются построчно между выходными данными различных сценариев, но они не искажаются и не чередуются в данной строке.

Странное поведение от parallel- я могу подать отчет об ошибке.

Cera
источник