LINUX.ORG.RU

Библиотека для параллельного запуска участков BASH-кода

 


1

1

Напоминаю о том, что на github'е есть такая библиотека, которой всё более активно пользуюсь я сам и которая благодаря этому сейчас весьма удобна в использовании и традиционно для написанного мной кода - отличается весьма user-friendly интерфейсом.
Приглашаю всех желающих участвовать в доработке библиотеки.
Ссылка: https://github.com/DRVTiny/bash4-debug-infra/blob/master/parex.inc
Пример использования:

for ((i=1; i<=10; i++)); do
 push_task <<'EOPROC'
# LOOP BODY HERE
EOPROC
done
wait4_all_gone

★★★★★

Интересно, на bash можно написать компилятор С? Имею ввиду не запуск gcc $1 внутри, а полноценный компилятор.

nanoolinux ★★★★
()

Coprocesses A coprocess is a shell command preceded by the coproc reserved word. A coprocess is executed asyn- chronously in a subshell, as if the command had been terminated with the & control operator, with a two- way pipe established between the executing shell and the coprocess.

The format for a coprocess is:

coproc [NAME] command [redirections]

This creates a coprocess named NAME. If NAME is not supplied, the default name is COPROC. NAME must not be supplied if command is a simple command (see above); otherwise, it is interpreted as the first word of the simple command. When the coproc is executed, the shell creates an array variable (see Arrays below) named NAME in the context of the executing shell. The standard output of command is connected via a pipe to a file descriptor in the executing shell, and that file descriptor is assigned to NAME[0]. The stan- dard input of command is connected via a pipe to a file descriptor in the executing shell, and that file descriptor is assigned to NAME[1]. This pipe is established before any redirections specified by the com- mand (see REDIRECTION below). The file descriptors can be utilized as arguments to shell commands and redirections using standard word expansions. The process ID of the shell spawned to execute the coprocess is available as the value of the variable NAME_PID. The wait builtin command may be used to wait for the coprocess to terminate.

The return status of a coprocess is the exit status of command.

читал?

drBatty ★★
()
Ответ на: комментарий от nanoolinux

Интересно, на bash можно написать компилятор С?

да, можно. Гугли Тьюринг-полноту. Это доказано.

А зачем? Если прославится хочешь, пиши на sed или на brain fuck. (только погугли, может уже написали. Тетрис с морским боем уже есть).

drBatty ★★
()
Ответ на: комментарий от drBatty

Если прославится хочешь, пиши на sed или на brain fuck.

-  .... Здесь у нас Наполеон, там Цезарь, а вот там - программист, писавший компилятор C на brainfuck. 
- Вот же ж... Недолго, наверное, писал.
- Как ни странно, он успел реализовать C89 и уверенно шёл к C90, но первый же багрепорт .... Увы.
router ★★★★★
()
Последнее исправление: router (всего исправлений: 1)
Ответ на: комментарий от router

Как ни странно, он успел реализовать C89 и уверенно шёл к C90, но первый же багрепорт

слабак.

drBatty ★★
()
Ответ на: комментарий от drBatty

Читал. И какое это имеет отношение к решаемой задаче? Библиотека первоначально написана для того, чтобы выполнять любые куски кода, поданные с STDIN'а, в background'е, при этом ограничивая число одновременно запущенных процессов количеством доступных ядер CPU. Сейчас ограничение на кол-во процессов можно задавать как угодно, не только числом ядер, - тем не менее оно всё равно должно быть. При этом полезна функция wait4_all_gone, которая ждёт все оставшиеся невыполненными процессы после всех push_task'ов (другой wait, который wait4_cpu_free, используется неявно внутри самой процедуры push_task).
Про coproc знаю, сколько с ним проблем, тоже знаю по BASH hackers guide'у, но это совершенно о другом.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

т.е. получается, что твоя либа — это тот же coproc, только число процессов у тебя жёстко ограничено числом ядер(или как-то по другому)?

Про coproc знаю, сколько с ним проблем, тоже знаю по BASH hackers guide'у, но это совершенно о другом.

ну и как же ты решил эти проблемы?

Да и что за проблемы-то? Т.е. я прекрасно знаю, какие проблемы у костыля coproc, но AFAIK они в любом костыле будут. Т.е. это вовсе не проблема coproc, это скорее проблема архитектуры posix shell, которая изначально делалась однопотоковой.

drBatty ★★
()

Для 4-х CPU за сколько выполнится этот код?

for i in 10 1 1 1 10 1 1 1 10 1 1 1; do
push_task <<'EOPROC'
      sleep $i
EOPROC
done
wait4_all_gone
anonymous
()
Ответ на: комментарий от drBatty

т.е. получается, что твоя либа — это тот же coproc

Причём здесь вообще coproc? coproc - это нечто для запуска параллельного процесса и асинхронного «общения» с ним через хитрый редирект ввода-вывода.
То, что нужно мне, прекрасно реализуется простым «&», его я и использовал. Можно было бы заморочиться с coproc'ом вместо &, но мне это пока не нужно было, поскольку, повторюсь, задача была в том, чтобы запускать ограниченное число параллельных процессов и ждать, когда они завершатся. В принципе вместо wait4_all_gone можно было бы использовать тупо просто wait, но тогда нужно было бы рассчитывать на то, что процессы иначе как push_task'ом не запускаются.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anonymous
akkerman@debian:~$ time ( for i in 10 1 1 1 10 1 1 1 10 1 1 1; do
push_task <<'EOPROC'
      sleep $i
EOPROC
 done; wait4_all_gone; )


real	0m12.036s
user	0m0.204s
sys	0m0.296s
DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от drBatty

Дык это... А ты сам-то чего хочешь, чего не хватает для счастья в плане параллельного программирования на BASH'е?
Мне вот жестоко не хватает версии wait'а, которая бы завершалась при завершении любого, а не всех процессов из переданного ей списка. Из-за этого мне пришлось использовать дикий костыль со sleep 0.01, иначе циклы CPU пожирал постоянный поллинг.
Ещё не хватает очень сильно, до омерзения просто, версии демона at, у которой дискретность была бы не в минуты (это просто пц какой-то, так нельзя!), а хотя бы в миллисекунды. Тогда я мог бы ставить «будильник», посылающий процессу сигнал или ещё что-то разумное делающий, перед входом в сомнительный участок кода, который может «подвиснуть», и снимать этот будильник на выходе. C использованием at традиционного at это делается просто, но со временем там всё ОЧЕНЬ плохо.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от drBatty

ясно всё с тобой.

А то, что «параллельные» участки кода автоматически получают копии всех переменных порождающего процесса, тоже понятно? Просто тот же parallel так не умеет: там если не export'ируешь всё, что тебе нужно, в т.ч. используемые процедуры, то придётся писать этот самый параллелизуемый код как «чёрный ящик» функционального программирования: ничего, кроме своих входных параметров, он узнать не сможет.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

Дык это... А ты сам-то чего хочешь, чего не хватает для счастья в плане параллельного программирования на BASH'е?

а мне всё и так нравится. Т.е. оно конечно кривое в bash, но если его мало, то есть более другие ЯП.

Мне вот жестоко не хватает версии wait'а, которая бы завершалась при завершении любого, а не всех процессов из переданного ей списка.

Тогда я мог бы ставить «будильник», посылающий процессу сигнал

это всё конечно нужно, но ИМХО не для bash.

drBatty ★★
()
Ответ на: комментарий от drBatty

BASH отлично подходит для любых скриптов администрирования и обработки текстовых данных (а лучше совмещающих то и другое), в которых не критично время исполнения собственно BASH-кода. Т.е. там, где время, которое тратится вызываемыми из скрипта программами всё равно неизмеримо больше или там, где объёмы входных текстовых данных небольшие и компактность/простота кода критичнее, нежели оперативность получения результата (относится, например, ко многим чудесным cron-заданиям обработки логов, запускаемым «раз в час»).
В целом хотя я неплохо программлю и на Perl - BASH мне нравится куда больше тем, что в нём всё-таки делается упор на реализацию поставленной задачи, а не на то, что there is more than one way to do it. И с Perl'ом, как я уже неоднократно замечал, любая более-менее сложная программа очень скоро начинает просто-таки ломиться от use ModuleX, причём ModuleX как правило зависит ещё от >9000 ModuleY и ModuleZ'ов, что особенно доставляет, когда нужен весьма ограниченный функционал из ModuleX. В BASH такого holy shit'а нет, хотя есть конечно упёртые люди типа меня, пишущие библиотеки (но эти библиотеки малы по размерам и чаще всего вообще не имеют зависимостей).

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

Мне вот жестоко не хватает версии wait'а, которая бы завершалась при завершении любого, а не всех процессов из переданного ей списка. Из-за этого мне пришлось использовать дикий костыль со sleep 0.01, иначе циклы CPU пожирал постоянный поллинг.

Так попробуй (особо не проверял)

trap "echo debug_free_CPU" SIGUSR1

wait4_cpu_free () {
    wait
    rotate_tq
}
for ((i=1; i<=10; i++)); do
 push_task <<'EOPROC'
# LOOP BODY HERE
kill -USR1 $$
EOPROC
done
wait4_all_gone
anonymous
()
Ответ на: комментарий от drBatty

это всё конечно нужно, но ИМХО не для bash.

А как же простейший случай SystemV скрипта, который посылает kill предыдущему инстансу своего демона и ждёт высвобождения ресурсов? Можно ждать в цикле на определённое количество итераций с каким-то временем задержки (sleep 0.1), но если реальное время старта демона равно 0.001 секунде, то в большинстве случаев мы просто потеряем время 0.1 - 0.001 на тупое ожидание завершения команды sleep. А если бы запустили бесконечный цикл ожидания, поставив перед ним «будильник» на случай, если демон завис и какой-нибудь pid-файл или сокет убрать не сможет - то в большинстве случаев уже на какой-нибудь 5-й итерации благополучно вышли бы из этого цикла, узрев, что thats ok.
И что, по вашему подобную задачу должно решать на каком языке программирования?

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Поправка

EOPROC
done
trap '' SIGUSR1
wait4_all_gone
anonymous
()
Ответ на: комментарий от DRVTiny

Не надо метать бисер перед drBatty, он в последнее время строит из себя всезнайку, а на деле занимается демагогией.

anonymous
()
Ответ на: комментарий от anonymous

Так попробуй (особо не проверял)

Идею понял: отправили сигнал из дочернего, просмотрели очередь в родителе, узнали, кто «gone» и подкорректировали очередь процессов TASKS_QUEUE. В этом есть смысл, и даже довольно большой, но тогда нужно, чтобы дочерние процессы изначально проектировались так, чтобы в конце своей работы они посылали сигнал завершения «родителю». Чтобы не учитывать ситуацию «оно рухнуло не вовремя», можно делать trap «kill -USR1 $$» EXIT в дочернем процессе...
Вот только плохо то, что дочерний процесс должен уже проектироваться специально для использования внутри логики push_task. Ммм... Но вообще асинхронное взаимодействие через сигналы - идея неплохая. Надо будет проверить, не получит ли этот USR1 и сам процесс-потомок по цепочке от «родителя». Вроде бы бред, но я уже сталкивался с такой ситуацией - там, правда, это был сигнал HUP.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anonymous

А почему wait должен завершиться после получения родительским процессом сигнала USR1?? wait ведь ждёт завершения вообще всех процессов в background'е, если ему список не передать (а если список - то вообще всех в списке)
Просто в $$ у потомка будет именно PID родителя, а не свой, это особенность такая BASH'а. Собственный PID у потомка лежит в BASHPID'е

DRVTiny ★★★★★
() автор топика
Последнее исправление: DRVTiny (всего исправлений: 1)
Ответ на: комментарий от DRVTiny

Мне вот жестоко не хватает версии wait'а, которая бы завершалась при завершении любого, а не всех процессов из переданного ей списка. Из-за этого мне пришлось использовать дикий костыль со sleep 0.01, иначе циклы CPU пожирал постоянный поллинг.

А то, что «параллельные» участки кода автоматически получают копии всех переменных порождающего процесса, тоже понятно?

Это именно копии? А то можно было бы запускать

  sleep 100000 &
  waitwar=$!
  fg

А в конце всех подпроцессов убивать $waitwar

ziemin ★★
()
Ответ на: комментарий от DRVTiny

Те изменения что я тебе дал уже работают. Почему builtin wait должен завершиться? Потому что так в man bash написано :-)

anonymous
()
Ответ на: комментарий от ziemin

Хотя что это я. Можно через файл pid передавать :)

ziemin ★★
()
Ответ на: комментарий от DRVTiny

Просто в $$ у потомка будет именно PID родителя, а не свой,

Что-то ты тупишь, с drBatty переобщался :-)

Нахера потомку самому себе сигнал слать? Родителю надо послать, чтобы он среагировал на смерть потомка.

anonymous
()
Ответ на: комментарий от anonymous

Родителю надо послать, чтобы он среагировал на смерть потомка.

А профит какой? Получается экономия только на том, что к моменту вызова push_task массив TASKS_QUEUE не будет содержать те процессы, которые завершились, сообщив об этом родителю
Ну ок, а что это даёт? Типа если потомок достаточно умный, чтобы уметь посылать сигналы родителю, то мы, возможно, сразу затолкнем в TASKS_QUEUE ещё один процесс, а не будем делать wait4_cpu_free? Но в принципе если потомок завершился, то мы это с небольшими накладными расходами итак выясним после первого же прохода по TASKS_QUEUE.
А вот от бесконечного перебора очереди задач в случае если на момент push_task'а при N задач = MAX_TASKS ещё ни одна из них не завершилась - нас ведь это не избавит. Пусть даже мы просто делаем

wait
rotate_tq
И как мы перейдём к выполнению rotate_tq в случае получения сигнала???

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

просто внеси изменения и проверь иначе мне придется тебе ман зачитывать. сказад же что оно работает и профит на задаче 10 1 1.... systime на порядок меньше, т. к. нет форков sleep 0.01

anonymous
()
Ответ на: комментарий от DRVTiny

А как же простейший случай SystemV скрипта, который посылает kill предыдущему инстансу своего демона и ждёт высвобождения ресурсов?

не очень понимаю: что тебе мешает сделать паузу в 1ms? и крутить свой бесконечный цикл?

drBatty ★★
()

Странно, что заставляет лить воду на мельницу bash - как языка
напрочь с отсутствующими понятиями скорости, больше tcl по бинарю и явно дурнее его по возможностям ?))
И причем еще изобретать механизмы известные в тикле сто лет ?
Смысл сего действа ?

bedcasus
()

О, это интересно, а heredoc обязательно должен быть текстом, без exapansion? В смысле вот так сделать

myfunc() {
    echo $1
}

for item in $list; do
    push_task <<EOPROC
myfunc $item
EOPROC
done
wait4_all_gone
не получится?

Deleted
()
Ответ на: комментарий от Deleted

Получится! Можно передавать любой код через STDIN или из файла (push_task -f FILE). Главное - чётко понимать, в какой момент произойдёт expansion: в вашем случае значение переменной $item будет подставлено _до_ передачи текста в push_task, так что реальный код будет выглядеть, например, как:

myfunc 'NifNif'

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Действительно, отправка сигнала родителю работает! А теперь какой man читать? :)

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от bedcasus

Ваш tcl никому даром не нужен, на нём никто ничего не пишет. Сравните это объёмами кода на BASH и выходите в сад покурить.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

Предлагаете горбатого к стене клеить ?
Я люблю тишину, но не сторонник тихого ужаса.))

bedcasus
()
Ответ на: комментарий от anonymous

Благодаря замечательному умному анонимусу я закоммитил новую версию parex.inc'а.
Теперь поддерживаются «умные» задачи, которые в конце своего исполнения отправят порождающему процессу сигнал
Подумываю над тем, чтобы внедрять в процессы команду kill -USR1 самому, но только при наличии отдельной на то опции у push_task'а, разрешающей это делать.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Ненужности анонимусов? Так они же разные бывают. Есть такие типа вас, тупо срущие в комментах, а есть вполне дельные советы дающие.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от anarquista

1) Не нужно export'ировать все переменные: код выполняется как обычный source, все переменные доступны параллелизованному участку кода на BASH (это копии переменных родительского процесса, так что изменять их смысла нет)
2) Можно встраивать параллелизованный код в скрипте так, как если бы он выполнялся последовательно и не отличался визуально от остального кода
3) push_task просто исполняет участок кода параллельно, а не пытается изображать из себя аналог xargs. Это соответствует принципу KISS и принципу бритвы Оккама.

DRVTiny ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.