Vamos dizer que tenho um loop no Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
é vinculado à CPU e eu tenho um processador de 4 núcleos bem brilhante Eu gostaria de poder executar até 4 de do-something
uma vez.
A abordagem ingênua parece ser:
for foo in `some-command`
do
do-something $foo &
done
Isso executará todos os do-something
s de uma vez, mas existem algumas desvantagens, principalmente que o do-something também pode ter algum I / O significativo que pode ser um pouco mais lento ao executar tudo de uma vez. O outro problema é que esse bloco de código retorna imediatamente, então não há como fazer outro trabalho quando todos os do-something
s estiverem concluídos.
Como você escreveria esse loop para que sempre houvesse do-something
Xs em execução ao mesmo tempo?
Respostas:
Dependendo do que você deseja fazer o xargs também pode ajudar (aqui: convertendo documentos com pdf2ps):
cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w ) find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus pdf2ps
Dos documentos:
--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.
fonte
find [...] -print0
exargs -0
.cpus=$(getconf _NPROCESSORS_ONLN)
--max-procs=0
para obter o máximo de processos possível?--max-procs=0
é mais parecido com a tentativa do questionador (inicie tantos processos quanto argumentos).Com GNU Parallel http://www.gnu.org/software/parallel/ você pode escrever:
GNU Parallel também suporta a execução de trabalhos em computadores remotos. Isso executará um por núcleo de CPU nos computadores remotos - mesmo se eles tiverem um número diferente de núcleos:
Um exemplo mais avançado: Aqui, listamos os arquivos nos quais queremos que my_script seja executado. Os arquivos têm extensão (talvez .jpeg). Queremos que a saída de my_script seja colocada ao lado dos arquivos em basename.out (por exemplo, foo.jpeg -> foo.out). Queremos executar my_script uma vez para cada núcleo do computador e também queremos executá-lo no computador local. Para os computadores remotos, queremos que o arquivo seja processado e transferido para o computador fornecido. Quando my_script terminar, queremos que foo.out seja transferido de volta e, em seguida, que foo.jpeg e foo.out sejam removidos do computador remoto:
cat list_of_files | \ parallel --trc {.}.out -S server1,server2,: \ "my_script {} > {.}.out"
O GNU Parallel garante que a saída de cada trabalho não seja combinada, então você pode usar a saída como entrada para outro programa:
Veja os vídeos para mais exemplos: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
fonte
find
comando para gerar uma lista de arquivos, porque não apenas evita o problema quando há um espaço dentro de um nome de arquivo que ocorre em,for i in ...; do
mas o find também pode fazer ofind -name \*.extension1 -or -name \*.extension2
que o GNU parallel {.} Pode lidar muito bem.cat
seja, obviamente, inútil.fonte
Aqui está uma solução alternativa que pode ser inserida em .bashrc e usada para um liner diário:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
Para usá-lo, basta colocar
&
após os jobs e uma chamada pwait, o parâmetro fornece o número de processos paralelos:for i in *; do do_something $i & pwait 10 done
Seria melhor usar em
wait
vez de ficar ocupado aguardando a saída dejobs -p
, mas não parece haver uma solução óbvia para esperar até que qualquer um dos trabalhos fornecidos seja concluído em vez de todos eles.fonte
Em vez de um bash simples, use um Makefile e especifique o número de trabalhos simultâneos com, em
make -jX
que X é o número de trabalhos a serem executados de uma vez.Ou você pode usar
wait
("man wait
"): iniciar vários processos filhos, chamarwait
- ele sairá quando os processos filhos terminarem.maxjobs = 10 foreach line in `cat file.txt` { jobsrunning = 0 while jobsrunning < maxjobs { do job & jobsrunning += 1 } wait } job ( ){ ... }
Se você precisar armazenar o resultado do trabalho, atribua o resultado a uma variável. Depois
wait
é só verificar o que a variável contém.fonte
Talvez tente um utilitário de paralelização em vez de reescrever o loop? Sou um grande fã de xjobs. Eu uso xjobs o tempo todo para copiar arquivos em massa em nossa rede, geralmente ao configurar um novo servidor de banco de dados. http://www.maier-komor.de/xjobs.html
fonte
Se você está familiarizado com o
make
comando, na maioria das vezes pode expressar a lista de comandos que deseja executar como um makefile. Por exemplo, se você precisa executar $ SOME_COMMAND em arquivos * .input, cada um dos quais produz * .output, você pode usar o makefilee então apenas corra
para executar no máximo NUMBER comandos em paralelo.
fonte
Embora
bash
seja provavelmente impossível fazer isso direito , você pode fazer um semi-certo com bastante facilidade.bstark
deu uma boa aproximação do direito, mas tem as seguintes falhas:Outra aproximação que não tem essas falhas é a seguinte:
scheduleAll() { local job i=0 max=4 pids=() for job; do (( ++i % max == 0 )) && { wait "${pids[@]}" pids=() } bash -c "$job" & pids+=("$!") done wait "${pids[@]}" }
Observe que este é facilmente adaptável para também verificar o código de saída de cada trabalho assim que termina, para que você possa avisar o usuário se um trabalho falhar ou definir um código de saída de
scheduleAll
acordo com a quantidade de trabalhos que falharam, ou algo assim.O problema com este código é apenas este:
Uma solução que cuida desse último problema teria que usar
kill -0
para pesquisar se algum dos processos desapareceu em vez dewait
e agendar o próximo trabalho. No entanto, isso introduz um pequeno problema novo: você tem uma condição de corrida entre o término de um trabalho e akill -0
verificação se ele foi encerrado. Se o trabalho terminar e outro processo em seu sistema iniciar ao mesmo tempo, pegando um PID aleatório que por acaso é o do trabalho que acabou de terminar, okill -0
usuário não notará que seu trabalho foi concluído e as coisas vão quebrar novamente.Uma solução perfeita não é possível em
bash
.fonte
função para 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 }
usando:
fonte
make -j
é inteligente, mas sem nenhuma explicação e aquele blob de código Awk somente para gravação, evito votos positivos.O projeto no qual trabalho usa o comando wait para controlar processos shell paralelos (ksh na verdade). Para atender às suas preocupações sobre IO, em um sistema operacional moderno, é possível que a execução paralela aumente a eficiência. Se todos os processos estiverem lendo os mesmos blocos no disco, apenas o primeiro processo terá que atingir o hardware físico. Os outros processos freqüentemente serão capazes de recuperar o bloco do cache de disco do sistema operacional na memória. Obviamente, ler da memória é várias ordens de magnitude mais rápido do que ler do disco. Além disso, o benefício não requer alterações de codificação.
fonte
Isso pode ser bom o suficiente para a maioria dos propósitos, mas não é o ideal.
#!/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
fonte
Aqui está como consegui resolver esse problema em um script 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
fonte
Muito tarde para a festa aqui, mas aqui está outra solução.
Muitas soluções não lidam com espaços / caracteres especiais nos comandos, não mantêm N jobs em execução o tempo todo, comem cpu em loops ocupados ou dependem de dependências externas (por exemplo, GNU
parallel
).Com inspiração para manipulação de processos mortos / zumbis , aqui está uma solução puramente 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 }
E uso de amostra:
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[@]}"
A saída:
Para manipulação de saída por processo
$$
pode ser usado para registrar em um arquivo, por exemplo:function job_done { cat "$1.log" } cmds=( \ "echo 1 \$\$ >\$\$.log" \ "echo 2 \$\$ >\$\$.log" \ ) run_parallel_jobs 2 "job_done" "${cmds[@]}"
Resultado:
fonte
Você pode usar um simples loop for aninhado (substitua N e M por inteiros apropriados abaixo):
for i in {1..N}; do (for j in {1..M}; do do_something; done & ); done
Isso executará do_something N * M vezes em M rodadas, cada rodada executando N tarefas em paralelo. Você pode tornar N igual ao número de CPUs que possui.
fonte
Minha solução para sempre manter um determinado número de processos em execução, manter o rastreamento de erros e lidar com processos ubinterruptíveis / zumbis:
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 }
Uso:
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"
fonte
$ DOMAINS = "lista de alguns domínios em comandos" para foo in
some-command
doeval `some-command for $DOMAINS` & job[$i]=$! i=$(( i + 1))
feito
Ndomains =
echo $DOMAINS |wc -w
para i em $ (seq 1 1 $ Ndomains) faça echo "esperar por $ {job [$ i]}" esperar "$ {job [$ i]}" concluído
neste conceito funcionará para o paralelizar. o importante é que a última linha de avaliação é '&', que colocará os comandos em segundo plano.
fonte