문제
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의 마지막 줄이 '&'라는 것입니다. 그러면 명령이 배경에 배치됩니다.