LINUX.ORG.RU

Запускалка параллельных задач

 


0

1

Вчера написал простенькую, но (для меня) уже вполне функциональную запускалку задач из bash-скриптов. Её предназначение простое: брать из стандартного потока ввода текст задачи (на том же bash) и запускать её «в фоне». Отличие от простого «амперсанда» простое, но существенное: количество одновременно запущенных таким образом задач должно быть меньше или равно количеству процессорных ядер. Если требуется запустить задачу тогда, когда все ядра уже заняты ранее запущенными ones, моя функция будет ждать освобождения хотя бы одного из ядер.
Собственно, актуальный текст данной функции можно будет всегда найти на github'е (https://github.com/DRVTiny/bash4-debug-infra), а сюда скопирую текущую версию просто для того, чтобы было понятно, о чём речь и как э то реализовано.
Если есть идеи, как реализовать чистку списка запущенных задач без дорогостоящего полинга - с использованием какого-нибудь SIGCHLD, например, - буду очень признателен.

declare -i CPU_KERNELS=$(fgrep processor /proc/cpuinfo | wc -l)
declare -A TASKS_QUEUE=()

unset wait4_cpu_free push_task

rotate_tq () {
 declare -i taskPID flTaskGone=0
 for taskPID in ${!TASKS_QUEUE[@]}; do
  [[ -f /proc/$taskPID/cmdline && ${TASKS_QUEUE[$taskPID]} == "$(</proc/$taskPID/cmdline)" ]] 2>/dev/null || {
   flTaskGone=1
   unset TASKS_QUEUE[$taskPID]
  }
 done
 return $flTaskGone
}

wait4_cpu_free () {
 while :; do
  rotate_tq || break
 done
 return 0
}

push_task () {
 local taskPID

 if (( ${#TASKS_QUEUE[@]} )); then
  rotate_tq
  (( ${#TASKS_QUEUE[@]} >= CPU_KERNELS )) && wait4_cpu_free
 fi

 eval "$(cat -)" &
 taskPID=$!
 [[ -f /proc/$taskPID/cmdline ]] && \
  TASKS_QUEUE[$taskPID]="$(</proc/$taskPID/cmdline)"
 return $?
}

★★★★★

чистку списка запущенных задач без дорогостоящего полинга

Можно прикручивать к каждой задаче в конце

echo $mypid Done >> /tmp/log

И потом tailf'ом отслеживать внвь завершившиеся.

И прикрути code=bash, пока не поздно - трудно читать без подсветки

ziemin ★★
()

Если есть идеи, как реализовать чистку списка запущенных задач без дорогостоящего полинга - с использованием какого-нибудь SIGCHLD, например, - буду очень признателен.

Я одно время делал через трап и sigchld:

set -o monitor
j=12 #number of "threads", lol
jj=0
i=0

function c_dead(){
        echo "trapped"
        jj=$((jj-1))
}

function c_start(){
        echo '2^2^20' | bc > /dev/null
}

while [[ "$i" -ne 256 ]];
do
        if [[ "$jj" -lt "$j" ]]; then
                c_start &
                jj=$((jj+1))
                i=$((i+1))
                echo "I: $i"
        fi
        trap c_dead SIGCHLD
done

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

wait в конце отклеился. Нужно дождаться завершения последних процессов.

cx ★★
()

как реализовать чистку списка запущенных задач

у bash есть wait если я правильно понял проблему

true_admin ★★★★★
()

moreutils/parallel ?

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

Выполняю код и не дадут мне соврать архангелы со всеми прочими контролирующими внеземными существами, что он точно такой:

#!/bin/bash
set -bm +H
set -o monitor
a=
hSIGCHLD () {
 a+=" $0"
}
trap hSIGCHLD SIGCHLD
( sleep 2; pwd; ) &
for ((i=0; i<8; i++)); do
 sleep 1; echo -n =
done
echo 'I am parent at the end of my life :('
echo "a=\"$a\""
А на выходе я получаю:
==/home/akkerman
======I am parent at the end of my life :(
a=" ./1.sh ./1.sh ./1.sh ./1.sh ./1.sh ./1.sh ./1.sh ./1.sh ./1.sh"
А должно быть:
a=" ./1.sh"
SIGCHLD, видимо, срабатывает и по такому печальному поводу, как завершение обработчика SIGCHLD, что по существу просто какой-то fucking shit, простите мне мой «немецкий» :(

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

у bash есть wait если я правильно понял проблему

Это верно. Загвоздка в том, что мы не знаем, какой процесс завершится первым. То есть нельзя просто взять PID первой в очереди задачи и подождать её завершения, потому что за это время может завершиться другая задача.
Концептуально верным видится решение, предполагающее наличие «обратной связи», а именно сами задачи должны так или иначе сообщить основному процессу о том, что они завершились, и это должно быть обработано основным процессом асинхронно, через механизм, подобный механизму прерываний. Например, через перехват сигналов. Вот как раз здесь на сцене появляется сигнал SIGCHLD, единственной проблемой которого является то, что он эту сцену вовремя не покидает... :)

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

Загвоздка в том, что мы не знаем, какой процесс завершится первым. То есть нельзя просто взять PID первой в очереди задачи и подождать её завершения, потому что за это время может завершиться другая задача.

Решал давеча похожую задачку и проклял башевский wait за то, что он теряет нужную фичу. В сях простой wait() ждёт завершения любого дочернего процесса и возвращает его pid. В баше можно либо подождать конкретного, либо подождать любого, но не узнаешь его pid :(

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

...но если есть список процессов с PID'ами, то легко проверить, какой из них завершился.

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

man bash-builtins:

If n is not given, all currently active child processes are waited for, and the return status is zero
Это совсем не то :(
Только что проверил: действительно, wait ждёт завершения всех дочерних процессов.

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

Её предназначение простое: брать из стандартного потока ввода текст задачи (на том же bash) и запускать её «в фоне». Отличие от простого «амперсанда» простое, но существенное: количество одновременно запущенных таким образом задач должно быть меньше или равно количеству процессорных ядер. Если требуется запустить задачу тогда, когда все ядра уже заняты ранее запущенными ones, моя функция будет ждать освобождения хотя бы одного из ядер.

а что это даёт? К тому-же, чаще всего скорость упирается НЕ в ядра, а в сеть или в накопитель например. Если у тебя два ядра, и 10 процессов, которые ждут сеть, то почему их все 10 сразу не запустить? Какая разница в этом случае, сколько у тебя ядер?

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

В баше можно либо подождать конкретного, либо подождать любого, но не узнаешь его pid :(

почитай man bash про COPROC

   Coprocesses
       A  coprocess  is  a shell command preceded by the coproc reserved word.  A coprocess is executed asynchronously in a sub‐
       shell, 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 standard 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 spec‐
       ified  by  the  command (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 ★★
()
Ответ на: комментарий от DRVTiny

а именно сами задачи должны так или иначе сообщить основному процессу о том, что они завершились

может пусть у каждой задачи будет свой контейнер-впраппер который будет ее запускать и который знает о существовании главного процесса и сообщит ему?

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

почитай man bash про COPROC

Я вот почитал, но почему-то не понял, что это даёт в контексте задачи запускать не более N параллельных процессов за раз.

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

контейнер-впраппер который будет ее запускать и который знает о существовании главного процесса и сообщит ему?

Кстати, это было бы правильно даже несмотря на то, что порождает лишнюю сущность «контейнера». Мешает сильно только то, что в ожидании асинхронного сигнала главный процесс что-то должен делать... И если во время wait он просто блокируется и не кушает процессорные циклы, то «бесконечный» цикл ожидания изменения переменной в результате асинхронного вызова обработчика сигнала - приведёт нас примерно к тому же, что мы имеем сейчас: к разбазариванию процессорного времени на ожидание невесть знает чего.

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

Я вот почитал, но почему-то не понял, что это даёт в контексте задачи запускать не более N параллельных процессов за раз.

ты их можешь поименовать, а потом отслеживать, сколько у тебя сейчас процессов запущено, и каких именно. Как обычно, PID посл. процесса в $?. Этого тоже. А coproc позволяет с ним взаимодействовать в потоках.

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

к разбазариванию процессорного времени на ожидание невесть знает чего.

Без разбазаривания не обойтись, bash не умеет аналога ″sigsuspend()″, сигнал может придти в неподходящий момент — между проверкой условия, что потомки есть и запуском ″wait″.

mky ★★★★★
()

Внимание, правильный ответ!
GNU Parallel
Делает абсолютно то же самое, что и push_task (отличие только в том, что parallel зачем-то эмулирует xargs - ну, может, это и удобно).
Но быстрее. Потому что не тратит циклы процессора (в данном случае - процентов 40 циклов одного ядра) на тупой поллинг.
Жаль конечно, что я свою утилиту написал зря, но зато много полезного узнал...

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