как написать командную оболочку для пула процессов - PullRequest
31 голосов
/ 22 июня 2011

У меня более 10 задач для выполнения, и система ограничивает возможность одновременной работы максимум 4 задач.

Моя задача может быть запущена как: myprog имя задачи

Как мне написать скрипт оболочки bash для запуска этих задач. Наиболее важным является то, что когда одна задача завершается, сценарий может немедленно запустить другую, в результате чего количество запущенных задач всегда остается равным 4.

Ответы [ 10 ]

45 голосов
/ 27 октября 2013

Использование xargs:

xargs -P <maximun-number-of-process-at-a-time> -n <arguments per process> <commnad>

Подробности здесь .

26 голосов
/ 09 августа 2012

Я случайно наткнулся на этот поток, изучая при написании своего собственного пула процессов, и мне особенно понравилось решение Брэндона Хорсли, хотя я не мог правильно настроить сигналы, поэтому я черпал вдохновение в Apache и решил попробовать модель с предварительными форками.fifo как моя очередь заданий.

Следующая функция - это функция, которую рабочие процессы запускают при разветвлении.

# \brief the worker function that is called when we fork off worker processes
# \param[in] id  the worker ID
# \param[in] job_queue  the fifo to read jobs from
# \param[in] result_log  the temporary log file to write exit codes to
function _job_pool_worker()
{
    local id=$1
    local job_queue=$2
    local result_log=$3
    local line=

    exec 7<> ${job_queue}
    while [[ "${line}" != "${job_pool_end_of_jobs}" && -e "${job_queue}" ]]; do
        # workers block on the exclusive lock to read the job queue
        flock --exclusive 7
        read line <${job_queue}
        flock --unlock 7
        # the worker should exit if it sees the end-of-job marker or run the
        # job otherwise and save its exit code to the result log.
        if [[ "${line}" == "${job_pool_end_of_jobs}" ]]; then
            # write it one more time for the next sibling so that everyone
            # will know we are exiting.
            echo "${line}" >&7
        else
            _job_pool_echo "### _job_pool_worker-${id}: ${line}"
            # run the job
            { ${line} ; } 
            # now check the exit code and prepend "ERROR" to the result log entry
            # which we will use to count errors and then strip out later.
            local result=$?
            local status=
            if [[ "${result}" != "0" ]]; then
                status=ERROR
            fi  
            # now write the error to the log, making sure multiple processes
            # don't trample over each other.
            exec 8<> ${result_log}
            flock --exclusive 8
            echo "${status}job_pool: exited ${result}: ${line}" >> ${result_log}
            flock --unlock 8
            exec 8>&-
            _job_pool_echo "### _job_pool_worker-${id}: exited ${result}: ${line}"
        fi  
    done
    exec 7>&-
}

Вы можете получить копию моего решения в Github.Вот пример программы, использующей мою реализацию.

#!/bin/bash

. job_pool.sh

function foobar()
{
    # do something
    true
}   

# initialize the job pool to allow 3 parallel jobs and echo commands
job_pool_init 3 0

# run jobs
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run sleep 3
job_pool_run foobar
job_pool_run foobar
job_pool_run /bin/false

# wait until all jobs complete before continuing
job_pool_wait

# more jobs
job_pool_run /bin/false
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run foobar

# don't forget to shut down the job pool
job_pool_shutdown

# check the $job_pool_nerrors for the number of jobs that exited non-zero
echo "job_pool_nerrors: ${job_pool_nerrors}"

Надеюсь, это поможет!

15 голосов
/ 15 марта 2013

Используя GNU Parallel, вы можете сделать:

cat tasks | parallel -j4 myprog

Если у вас 4 ядра, вы можете просто:

cat tasks | parallel myprog

С http://git.savannah.gnu.org/cgit/parallel.git/tree/README:

Полная установка

Полная установка GNU Parallel так же проста, как:

./configure && make && make install

Персональная установка

Если вы не root, вы можете добавить ~ / bin к вашему пути и установить в ~ / bin и ~ / share:

./configure --prefix=$HOME && make && make install

Или, если в вашей системе нет 'make', вы можете просто скопировать src / parallel src / sem src / niceload src / sql в каталог на вашем пути.

Минимальная установка

Если вам просто нужна параллель и у вас не установлен 'make' (возможно, система старая или Microsoft Windows):

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
mv parallel sem dir-in-your-$PATH/bin/

Проверка установки

После этого вы сможете сделать:

parallel -j0 ping -nc 3 ::: foss.org.my gnu.org freenetproject.org

Это отправит 3 пакета ping на 3 разных хоста параллельно и напечатает вывод, когда они завершают.

Посмотрите вступительное видео для быстрого ознакомления: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

3 голосов
/ 22 июня 2011

Я бы предложил написать четыре сценария, каждый из которых выполняет определенное количество задач последовательно.Затем напишите другой сценарий, который запускает четыре сценария параллельно.Например, если у вас есть скрипты, script1.sh, script2.sh, script3.sh и script4.sh, вы можете использовать скрипт с именемheadcript.sh, например, так:

#!/bin/sh
./script1.sh & 
./script2.sh & 
./script3.sh & 
./script4.sh &
1 голос
/ 15 августа 2018

После ответа @ Parag Sardas ' и документации, приведенной здесь, приведен быстрый сценарий, который вы, возможно, захотите добавить в свой .bash_aliases.

Пересмотр ссылки на документ , потому что стоит прочитать

#!/bin/bash
# https://stackoverflow.com/a/19618159
# https://stackoverflow.com/a/51861820
#
# Example file contents:
# touch /tmp/a.txt
# touch /tmp/b.txt

if [ "$#" -eq 0 ];  then
  echo "$0 <file> [max-procs=0]"
  exit 1
fi

FILE=${1}
MAX_PROCS=${2:-0}
cat $FILE | while read line; do printf "%q\n" "$line"; done | xargs --max-procs=$MAX_PROCS -I CMD bash -c CMD

т.е. ./xargs-parallel.sh jobs.txt 4 максимум 4 процесса, прочитанных из jobs.txt

1 голос
/ 23 июня 2011

Этот проверенный скрипт запускает 5 заданий за раз и перезапускает новое задание, как только оно это делает (из-за отключения сна 10.9, когда мы получаем SIGCHLD. В более простой версии этого может использоваться прямой опрос спать 10,9, спать 1 и избавиться от ловушки).

#!/usr/bin/bash

set -o monitor
trap "pkill -P $$ -f 'sleep 10\.9' >&/dev/null" SIGCHLD

totaljobs=15
numjobs=5
worktime=10
curjobs=0
declare -A pidlist

dojob()
{
  slot=$1
  time=$(echo "$RANDOM * 10 / 32768" | bc -l)
  echo Starting job $slot with args $time
  sleep $time &
  pidlist[$slot]=`jobs -p %%`
  curjobs=$(($curjobs + 1))
  totaljobs=$(($totaljobs - 1))
}

# start
while [ $curjobs -lt $numjobs -a $totaljobs -gt 0 ]
 do
  dojob $curjobs
 done

# Poll for jobs to die, restarting while we have them
while [ $totaljobs -gt 0 ]
 do
  for ((i=0;$i < $curjobs;i++))
   do
    if ! kill -0 ${pidlist[$i]} >&/dev/null
     then
      dojob $i
      break
     fi
   done
   sleep 10.9 >&/dev/null
 done
wait
1 голос
/ 22 июня 2011

Возможно, вы могли бы сделать что-то умное с сигналами.

Обратите внимание, что это только для иллюстрации концепции, и, следовательно, не полностью проверены.

#!/usr/local/bin/bash

this_pid="$$"
jobs_running=0
sleep_pid=

# Catch alarm signals to adjust the number of running jobs
trap 'decrement_jobs' SIGALRM

# When a job finishes, decrement the total and kill the sleep process
decrement_jobs()
{
  jobs_running=$(($jobs_running - 1))
  if [ -n "${sleep_pid}" ]
  then
    kill -s SIGKILL "${sleep_pid}"
    sleep_pid=
  fi
}

# Check to see if the max jobs are running, if so sleep until woken
launch_task()
{
  if [ ${jobs_running} -gt 3 ]
  then
    (
      while true
      do
        sleep 999
      done
    ) &
    sleep_pid=$!
    wait ${sleep_pid}
  fi

  # Launch the requested task, signalling the parent upon completion
  (
    "$@"
    kill -s SIGALRM "${this_pid}"
  ) &
  jobs_running=$((${jobs_running} + 1))
}

# Launch all of the tasks, this can be in a loop, etc.
launch_task task1
launch_task tast2
...
launch_task task99
0 голосов
/ 12 июня 2019

Вот мое решение. Идея довольно проста. Я создаю fifo как семафор, где каждая строка обозначает доступный ресурс. При read постановке в очередь основной процесс блокируется, если ничего не осталось. И мы возвращаем ресурс после выполнения задачи, просто echo помещая что-либо в очередь.

function task() {
    local task_no="$1"
    # doing the actual task...
    echo "Executing Task ${task_no}"
    # which takes a long time
    sleep 1
}

function execute_concurrently() {
    local tasks="$1"
    local ps_pool_size="$2"

    # create an anonymous fifo as a Semaphore
    local sema_fifo
    sema_fifo="$(mktemp -u)"
    mkfifo "${sema_fifo}"
    exec 3<>"${sema_fifo}"
    rm -f "${sema_fifo}"

    # every 'x' stands for an available resource
    for i in $(seq 1 "${ps_pool_size}"); do
        echo 'x' >&3
    done

    for task_no in $(seq 1 "${tasks}"); do
        read dummy <&3 # blocks util a resource is available
        (
            trap 'echo x >&3' EXIT # returns the resource on exit
            task "${task_no}"
        )&
    done
    wait # wait util all forked tasks have finished
}

execute_concurrently 10 4

Приведенный выше скрипт будет запускать 10 задач и 4 каждый раз одновременно. Вы можете изменить последовательность $(seq 1 "${tasks}") на текущую очередь задач, которую вы хотите запустить.

0 голосов
/ 17 декабря 2013

Посмотрите на мою реализацию пула заданий в bash: https://github.com/spektom/shell-utils/blob/master/jp.sh

Например, чтобы запустить не более 3 процессов cURL при загрузке с большого количества URL-адресов, вы можете заключить команды cURL следующим образом:

./jp.sh "My Download Pool" 3 curl http://site1/...
./jp.sh "My Download Pool" 3 curl http://site2/...
./jp.sh "My Download Pool" 3 curl http://site3/...
...
0 голосов
/ 22 июня 2011

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

Основной скрипт создаст символические ссылки на исполняемые файлы в соответствии с определенным соглашением namimg.Например, первый префикс

ln -s executable1 ./01-task.01

предназначен для сортировки, а суффикс идентифицирует партию (01-04).Теперь мы создаем 4 сценария оболочки, которые принимают номер партии в качестве ввода и делают что-то вроде этого

for t in $(ls ./*-task.$batch | sort ; do
   t
   rm t
done
...