문제

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

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

do-something CPU에 바인딩되어 있고 반짝이는 멋진 4코어 프로세서를 가지고 있습니다.4개까지 달릴 수 있었으면 좋겠다 do-something즉시입니다.

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

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

이 실행됩니다 모두 do-something그러나 몇 가지 단점이 있습니다. 주로 수행하는 작업에는 성능을 저하시키는 상당한 I/O가 있을 수 있다는 것입니다. 모두 한 번에 조금 느려질 수 있습니다.또 다른 문제는 이 코드 블록이 즉시 반환되므로 모든 코드가 반환되면 다른 작업을 수행할 수 있는 방법이 없다는 것입니다. do-something끝났습니다.

항상 X가 있도록 이 루프를 어떻게 작성하시겠습니까? do-something한 번에 달려가는 거야?

도움이 되었습니까?

해결책

수행하려는 작업에 따라 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 병렬 사용 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 ] ; do
                jobcnt=(`jobs -p`)
                if [ ${#jobcnt[@]} -lt $maxjobs ] ; then
                        do-something $1 &
                        shift  
                else
                        sleep 1
                fi
        done
        wait
}

parallelize arg1 arg2 "5 args to third job" 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인 임의의 PID를 가져오면 kill -0 작업이 완료된 것을 눈치채지 못하고 상황이 다시 중단될 것입니다.

완벽한 솔루션은 불가능 bash.

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

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

%.output : %.input
    $(SOME_COMMAND) $< $@

all: $(OUTPUT)

그런 다음 그냥 실행

make -j<NUMBER>

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

배쉬에 대한 기능:

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

내가 작업하고 있는 프로젝트에서는 다음을 사용합니다. 기다리다 병렬 쉘(실제로는 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를 소모하거나, 외부 종속성에 의존하지 않습니다(예:암소 비슷한 일종의 영양 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 라운드에서 do_something N*M 번 실행되며, 각 라운드는 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

항상 주어진 수의 프로세스를 실행하고, 오류를 추적하고, 중단 불가능한/좀비 프로세스를 처리하는 내 솔루션은 다음과 같습니다.

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
}

용법:

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 = "명령어의 일부 도메인 목록" for foo in some-command하다

eval `some-command for $DOMAINS` &

    job[$i]=$!

    i=$(( i + 1))

완료

N도메인=echo $DOMAINS |wc -w

$(시퀀스 1 1 $Ndomains)의 i 하다 echo "${job[$i]} 대기" "${작업[$i]}" 대기 수행

이 개념에서는 병렬화에 적합합니다.중요한 것은 eval의 마지막 줄이 '&'라는 것입니다. 그러면 명령이 배경에 배치됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top