development

최대 프로세스 수로 Bash 스크립트 병렬화

big-blog 2020. 9. 24. 08:00
반응형

최대 프로세스 수로 Bash 스크립트 병렬화


Bash에 루프가 있다고 가정 해 보겠습니다.

for foo in `some-command`
do
   do-something $foo
done

do-somethingCPU에 묶여 있고 멋진 4 코어 프로세서가 있습니다. do-something한 번에 최대 4 개까지 실행하고 싶습니다 .

순진한 접근 방식은 다음과 같습니다.

for foo in `some-command`
do
   do-something $foo &
done

이 실행됩니다 모두 do-something 한 번에들하지만, 주로 몇 가지 단점이있다 할이-뭔가도 수행 할 몇 가지 중요한 I / O 할 수 있습니다 모든 조금 느려질 수 있습니다 한 번에 있습니다. 다른 문제는이 코드 블록이 즉시 반환되므로 모든 do-somethings가 완료 되면 다른 작업을 수행 할 방법이 없다는 것 입니다.

do-something한 번에 항상 X 가 실행 되도록이 루프를 어떻게 작성 하시겠습니까?


원하는 작업에 따라 xargs도 도움이 될 수 있습니다 (여기 : pdf2ps로 문서 변환).

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

문서에서 :

--max-procs=max-procs
-P max-procs
       Run up to max-procs processes at a time; the default is 1.
       If max-procs is 0, xargs will run as many processes as  possible  at  a
       time.  Use the -n option with -P; otherwise chances are that only one
       exec will be done.

GNU Parallel http://www.gnu.org/software/parallel/사용하면 다음과 같이 작성할 수 있습니다.

some-command | parallel do-something

GNU Parallel은 원격 컴퓨터에서 작업 실행도 지원합니다. 이렇게하면 원격 컴퓨터의 CPU 코어 당 하나씩 실행됩니다. 코어 수가 다른 경우에도 마찬가지입니다.

some-command | parallel -S server1,server2 do-something

고급 예 : 여기 my_script가 실행될 파일 목록이 있습니다. 파일 확장자는 .jpeg 일 수 있습니다. my_script의 출력이 basename.out의 파일 옆에 놓이기를 원합니다 (예 : foo.jpeg-> foo.out). 컴퓨터에있는 각 코어에 대해 my_script를 한 번씩 실행하고 로컬 컴퓨터에서도 실행하려고합니다. 원격 컴퓨터의 경우 파일이 주어진 컴퓨터로 전송되기를 원합니다. my_script가 완료되면 foo.out을 다시 전송하고 원격 컴퓨터에서 foo.jpeg 및 foo.out을 제거하려고합니다.

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallel은 각 작업의 출력이 혼합되지 않도록하므로 출력을 다른 프로그램의 입력으로 사용할 수 있습니다.

some-command | parallel do-something | postprocess

더 많은 예를 보려면 동영상을 참조 하세요 . https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


maxjobs = 4
parallelize () {
        while [$ # -gt 0]; 하다
                jobcnt = (`작업 -p`)
                if [$ {# jobcnt [@]} -lt $ maxjobs]; 그때
                        뭔가 $ 1 &
                        시프트  
                그밖에
                        수면 1
                fi
        끝난
        기다림
}

arg1 arg2 "3 번째 작업에 5 개의 args"arg4 ...

일반 bash 대신 Makefile을 사용한 다음 동시 작업 수를 지정하십시오. make -jX여기서 X는 한 번에 실행할 작업 수입니다.

또는 wait( " man wait")를 사용할 수 있습니다 . 여러 하위 프로세스를 시작하고 호출 wait합니다. 하위 프로세스가 완료되면 종료됩니다.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

작업 결과를 저장해야하는 경우 결과를 변수에 할당하십시오. wait변수에 포함 된 내용을 확인한 .


다음은 .bashrc에 삽입 할 수 있고 매일 하나의 라이너에 사용할 수있는 대체 솔루션입니다.

function pwait() {
    while [ $(jobs -p | wc -l) -ge $1 ]; do
        sleep 1
    done
}

이를 사용하기 위해해야 ​​할 일은 &작업과 pwait 호출 뒤에 놓기 만하면됩니다. 매개 변수는 병렬 프로세스의 수를 제공합니다.

for i in *; do
    do_something $i &
    pwait 10
done

wait의 출력을 기다리는 대신 사용 하는 것이 더 jobs -p좋지만 주어진 작업이 모두 완료 될 때까지 기다리는 확실한 해결책은없는 것 같습니다.


루프를 다시 작성하는 대신 병렬화 유틸리티를 사용해보십시오. 나는 xjobs의 열렬한 팬입니다. 저는 항상 xjobs를 사용하여 네트워크를 통해 파일을 대량 복사합니다. 일반적으로 새 데이터베이스 서버를 설정할 때입니다. http://www.maier-komor.de/xjobs.html


이 작업을 올바르게 수행하는 bash것은 아마도 불가능할 수 있지만, 반 오른쪽을 상당히 쉽게 수행 할 수 있습니다. bstark권리에 대한 공정한 근사치를 제공했지만 다음과 같은 결함이 있습니다.

  • 단어 분할 : 인수에 공백, 탭, 줄 바꿈, 별표, 물음표 등의 문자를 사용하는 작업을 전달할 수 없습니다. 그렇게하면 예상치 못한 일이 발생할 수 있습니다.
  • 아무것도 배경 화하지 않기 위해 나머지 스크립트에 의존합니다. 그렇게하거나 나중에 그의 스 니펫 때문에 백그라운드 작업을 사용할 수 없다는 것을 잊었 기 때문에 백그라운드로 전송되는 스크립트에 무언가를 추가하면 모든 것이 깨질 것입니다.

이러한 결함이없는 또 다른 근사값은 다음과 같습니다.

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

이것은 작업이 끝날 때 각 작업의 종료 코드를 확인하는 데 쉽게 적용 할 수 있으므로 작업이 실패하면 사용자에게 경고하거나 scheduleAll실패한 작업의 양에 따라 종료 코드를 설정할 수 있습니다.

이 코드의 문제점은 다음과 같습니다.

  • 한 번에 4 개의 작업 (이 경우)을 예약 한 다음 4 개의 작업이 모두 종료 될 때까지 기다립니다. 일부는 다른 작업보다 빨리 완료 될 수 있으며, 이로 인해 이전 배치 중 가장 긴 배치가 완료 될 때까지 다음 4 개 작업 배치가 대기하게됩니다.

이 마지막 문제를 처리하는 솔루션 kill -0은 프로세스 대신 사라진 프로세스가 있는지 여부를 폴링 wait하고 다음 작업을 예약하는 데 사용해야 합니다 . 그러나 그것은 작은 새로운 문제를 소개합니다. 작업 종료와 종료 kill -0여부 확인 사이에 경쟁 조건 이 있습니다. 작업이 종료되고 시스템의 다른 프로세스가 동시에 시작되어 방금 완료된 작업의 PID를 무작위로 가져 kill -0오면 작업이 완료된 것을 알지 못하며 일이 다시 중단됩니다.

.NET에서는 완벽한 솔루션이 불가능합니다 bash.


make명령에 익숙한 경우 대부분의 경우 실행하려는 명령 목록을 makefile로 표현할 수 있습니다. 예를 들어 각각 * .output을 생성하는 * .input 파일에서 $ SOME_COMMAND를 실행해야하는 경우 makefile을 사용할 수 있습니다.

INPUT = a. 입력 b. 입력
OUTPUT = $ (INPUT : .input = .output)

%.출력 입력
    $ (SOME_COMMAND) $ <$ @

모두 : $ (OUTPUT)

그리고 그냥 실행

make -j <번호>

최대 NUMBER 개의 명령을 병렬로 실행합니다.


bash에 대한 기능 :

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all
}

사용 :

cat my_commands | parallel -j 4

내가 작업하는 프로젝트는 wait 명령을 사용하여 병렬 셸 (실제로 ksh) 프로세스를 제어합니다. IO에 대한 우려를 해결하기 위해 최신 OS에서 병렬 실행이 실제로 효율성을 높일 수 있습니다. 모든 프로세스가 디스크에서 동일한 블록을 읽는 경우 첫 번째 프로세스 만 물리적 하드웨어에 도달하면됩니다. 다른 프로세스는 종종 메모리의 OS 디스크 캐시에서 블록을 검색 할 수 있습니다. 분명히 메모리에서 읽는 것은 디스크에서 읽는 것보다 몇 배 더 빠릅니다. 또한이 혜택은 코딩 변경이 필요하지 않습니다.


이것은 대부분의 목적에 충분할 수 있지만 최적은 아닙니다.

#!/bin/bash

n=0
maxjobs=10

for i in *.m4a ; do
    # ( DO SOMETHING ) &

    # limit jobs
    if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
        wait # wait until all have finished (not optimal, but most times good enough)
        echo $n wait
    fi
done

여기 파티에 정말 늦었지만 여기에 또 다른 해결책이 있습니다.

많은 솔루션이 명령에서 공백 / 특수 문자를 처리하지 않거나, 항상 N 개의 작업을 실행하지 않거나, 바쁜 루프에서 CPU를 먹거나, 외부 종속성 (예 : GNU parallel) 에 의존하지 않습니다 .

함께 죽은 / 좀비 프로세스 처리를위한 영감 , 여기에 순수 bash는 솔루션입니다 :

function run_parallel_jobs {
    local concurrent_max=$1
    local callback=$2
    local cmds=("${@:3}")
    local jobs=( )

    while [[ "${#cmds[@]}" -gt 0 ]] || [[ "${#jobs[@]}" -gt 0 ]]; do
        while [[ "${#jobs[@]}" -lt $concurrent_max ]] && [[ "${#cmds[@]}" -gt 0 ]]; do
            local cmd="${cmds[0]}"
            cmds=("${cmds[@]:1}")

            bash -c "$cmd" &
            jobs+=($!)
        done

        local job="${jobs[0]}"
        jobs=("${jobs[@]:1}")

        local state="$(ps -p $job -o state= 2>/dev/null)"

        if [[ "$state" == "D" ]] || [[ "$state" == "Z" ]]; then
            $callback $job
        else
            wait $job
            $callback $job $?
        fi
    done
}

그리고 샘플 사용법 :

function job_done {
    if [[ $# -lt 2 ]]; then
        echo "PID $1 died unexpectedly"
    else
        echo "PID $1 exited $2"
    fi
}

cmds=( \
    "echo 1; sleep 1; exit 1" \
    "echo 2; sleep 2; exit 2" \
    "echo 3; sleep 3; exit 3" \
    "echo 4; sleep 4; exit 4" \
    "echo 5; sleep 5; exit 5" \
)

# cpus="$(getconf _NPROCESSORS_ONLN)"
cpus=3
run_parallel_jobs $cpus "job_done" "${cmds[@]}"

출력 :

1
2
3
PID 56712 exited 1
4
PID 56713 exited 2
5
PID 56714 exited 3
PID 56720 exited 4
PID 56724 exited 5

프로세스 별 출력 처리를 $$사용하여 파일에 기록 할 수 있습니다. 예를 들면 다음과 같습니다.

function job_done {
    cat "$1.log"
}

cmds=( \
    "echo 1 \$\$ >\$\$.log" \
    "echo 2 \$\$ >\$\$.log" \
)

run_parallel_jobs 2 "job_done" "${cmds[@]}"

산출:

1 56871
2 56872

간단한 중첩 for 루프를 사용할 수 있습니다 (아래에서 N과 M을 적절한 정수로 대체).

for i in {1..N}; do
  (for j in {1..M}; do do_something; done & );
done

이것은 M 라운드에서 N * M 번 do_something을 실행하고 각 라운드는 N 개의 작업을 병렬로 실행합니다. N을 보유한 CPU 수와 같게 만들 수 있습니다.


다음은 bash 스크립트에서이 문제를 해결하는 방법입니다.

 #! /bin/bash

 MAX_JOBS=32

 FILE_LIST=($(cat ${1}))

 echo Length ${#FILE_LIST[@]}

 for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) ));
 do
     JOBS_RUNNING=0
     while ((JOBS_RUNNING < MAX_JOBS))
     do
         I=$((${INDEX}+${JOBS_RUNNING}))
         FILE=${FILE_LIST[${I}]}
         if [ "$FILE" != "" ];then
             echo $JOBS_RUNNING $FILE
             ./M22Checker ${FILE} &
         else
             echo $JOBS_RUNNING NULL &
         fi
         JOBS_RUNNING=$((JOBS_RUNNING+1))
     done
     wait
 done

My solution to always keep a given number of processes running, keep tracking of errors and handle ubnterruptible / zombie processes:

function log {
    echo "$1"
}

# Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs
# Returns the number of non zero exit codes from commands
function ParallelExec {
    local numberOfProcesses="${1}" # Number of simultaneous commands to run
    local commandsArg="${2}" # Semi-colon separated list of commands

    local pid
    local runningPids=0
    local counter=0
    local commandsArray
    local pidsArray
    local newPidsArray
    local retval
    local retvalAll=0
    local pidState
    local commandsArrayPid

    IFS=';' read -r -a commandsArray <<< "$commandsArg"

    log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes."

    while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do

        while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do
            log "Running command [${commandsArray[$counter]}]."
            eval "${commandsArray[$counter]}" &
            pid=$!
            pidsArray+=($pid)
            commandsArrayPid[$pid]="${commandsArray[$counter]}"
            counter=$((counter+1))
        done


        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :)
            if kill -0 $pid > /dev/null 2>&1; then
                pidState=$(ps -p$pid -o state= 2 > /dev/null)
                if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then
                    newPidsArray+=($pid)
                fi
            else
                # pid is dead, get it's exit code from wait command
                wait $pid
                retval=$?
                if [ $retval -ne 0 ]; then
                    log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]."
                    retvalAll=$((retvalAll+1))
                fi
            fi
        done
        pidsArray=("${newPidsArray[@]}")

        # Add a trivial sleep time so bash won't eat all CPU
        sleep .05
    done

    return $retvalAll
}

Usage:

cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home"

# Execute 2 processes at a time
ParallelExec 2 "$cmds"

# Execute 4 processes at a time
ParallelExec 4 "$cmds"

$DOMAINS = "list of some domain in commands" for foo in some-command do

eval `some-command for $DOMAINS` &

    job[$i]=$!

    i=$(( i + 1))

done

Ndomains=echo $DOMAINS |wc -w

for i in $(seq 1 1 $Ndomains) do echo "wait for ${job[$i]}" wait "${job[$i]}" done

in this concept will work for the parallelize. important thing is last line of eval is '&' which will put the commands to backgrounds.

참고URL : https://stackoverflow.com/questions/38160/parallelize-bash-script-with-maximum-number-of-processes

반응형