최대 프로세스 수로 Bash 스크립트 병렬화
Bash에 루프가 있다고 가정 해 보겠습니다.
for foo in `some-command`
do
do-something $foo
done
do-something
CPU에 묶여 있고 멋진 4 코어 프로세서가 있습니다. do-something
한 번에 최대 4 개까지 실행하고 싶습니다 .
순진한 접근 방식은 다음과 같습니다.
for foo in `some-command`
do
do-something $foo &
done
이 실행됩니다 모두 do-something
한 번에들하지만, 주로 몇 가지 단점이있다 할이-뭔가도 수행 할 몇 가지 중요한 I / O 할 수 있습니다 모든 조금 느려질 수 있습니다 한 번에 있습니다. 다른 문제는이 코드 블록이 즉시 반환되므로 모든 do-something
s가 완료 되면 다른 작업을 수행 할 방법이 없다는 것 입니다.
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
'development' 카테고리의 다른 글
여러 번 붙여 넣기 (0) | 2020.09.24 |
---|---|
django 1.4-오프셋 순진 및 오프셋 인식 날짜 시간을 비교할 수 없음 (0) | 2020.09.24 |
MySQL "Ca n't reopen table"오류 해결 (0) | 2020.09.24 |
const에 대한 포인터 삭제 (T const *) (0) | 2020.09.24 |
사용자가 "로그인"되었는지 확인하는 방법은 무엇입니까? (0) | 2020.09.23 |