2009-09-21 10 views
41

Chciałbym zwiększyć efektywność sekcji mojego kodu. Zastanawiam się nad zrobieniem tego na wiele procesów i wykonanie ich 50/100 razy na raz, zamiast tylko raz.Rozwidlenie/procesy wielogatkowe | Bash

na przykład (pseudo)

for line in file; 
do 
foo; 
foo2; 
foo3; 
done 

ja tak przez pętlę prowadzony wielokrotnie. Wiem, że można to zrobić za pomocą rozwidlenia. Czy wyglądałoby to tak?

while(x <= 50) 
parent(child pid) 
{ 
    fork child() 
} 
child 
{ 
    do 
    foo; foo2; foo3; 
    done 
    return child_pid() 
} 

Czy też myślę o tym w niewłaściwy sposób?

Dzięki!

Odpowiedz

2

Pozwól mi spróbować Przykład

for x in 1 2 3 ; do { echo a $x ; sleep 1 ; echo b $x ; } & done ; sleep 10 

I używać jobs aby zobaczyć, co działa.

26

Nie znam żadnego wyraźnego połączenia fork w bashu. Prawdopodobnie chcesz dodać do polecenia, które chcesz uruchomić w tle, & . Można również użyć & na funkcje, które definiują w skrypcie bash:

do_something_with_line() 
{ 
    line=$1 
    foo 
    foo2 
    foo3 
} 

for line in file 
do 
    do_something_with_line $line & 
done 

EDIT: do wprowadzenia limitu na liczbę jednoczesnych procesów w tle, można spróbować czegoś takiego:

for line in file 
do 
    while [`jobs | wc -l` -ge 50 ] 
    do 
    sleep 5 
    done 
    do_something_with_line $line & 
done 
+1

pan miscapitalized się do_something ... nazwa ;-) –

+1

Got It - co, kiedy chcę się upewnić, jestem tylko uruchamianie 50 wystąpień na raz? I - gdy jeden z tych procesów zostanie ukończony, upewnij się, że pojawiło się 1 więcej. – Greg

+0

Użyj wbudowanego skryptu 'jobs' bash. –

46

Domyślnie w skryptach bash (nieinteraktywnych) kontrola JOB jest wyłączona, więc nie można wykonywać poleceń: zadanie, fg i bg.

Oto, co działa dobrze dla mnie:

#!/bin/sh 

set -m # Enable Job Control 

for i in `seq 30`; do # start 30 jobs in parallel 
    sleep 3 & 
done 

# Wait for all parallel jobs to finish 
while [ 1 ]; do fg 2> /dev/null; [ $? == 1 ] && break; done 

Ostatnia linia używa „fg” przynieść zadanie tła na pierwszy plan. Robi to w pętli, dopóki fg nie zwróci 1 ($? == 1), co ma miejsce, gdy nie ma już więcej zadań w tle.

+16

W skryptach bashu możesz używać 'wait', np .:' sleep 3 & WAITPID = $ !; poczekaj $ WAITPID' lub skoncentruj się na pidach w ten sposób 'WAITPIDS =" $ WAITPIDS "$!; ...; poczekaj $ WAITPIDS' –

+9

Lub po prostu "czekaj". – lethalman

+0

jak zrobić 1000 rzeczy, 50 na raz?w pętli o nazwie '$ (seq 1 1000)' – chovy

17

Z GNU Równolegle można zrobić:

cat file | parallel 'foo {}; foo2 {}; foo3 {}' 

to będzie działać jedno zadanie na każdy rdzeń procesora. Aby uruchomić 50 zrobić:

cat file | parallel -j 50 'foo {}; foo2 {}; foo3 {}' 

Watch Intro wideo, aby dowiedzieć się więcej:

http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

Z wyjątkiem ekranu nag, równoległość jest całkiem fajna. –

+1

Dodałbym, że równoległość jest już zainstalowana na większości systemów. Moja maszyna z systemem OS X 10.8.5 ją obsługuje. Nadszedł czas, aby odkurzyć pajęczyny z mojej powłoki i zaktualizować pętle do równoległego ... – labyrinth

+0

wydaje się to być bardzo nieprzyjemne, gdy używasz wyszukiwania/zamiany, które mają znaki, które muszą zostać usunięte. –

2

W oparciu o to, czego wszyscy wspólne udało mi się umieścić to razem:

#!/usr/bin/env bash 

VAR1="192.168.1.20 192.168.1.126 192.168.1.36" 

for a in $VAR1; do { ssh -t -t $a -l Administrator "sudo softwareupdate -l"; } & done; 
WAITPIDS="$WAITPIDS "$!;...; wait $WAITPIDS 
echo "Script has finished" 

Exit 1 

Zawiera listę wszystkich aktualizacji na komputerze Mac na trzech komputerach jednocześnie. Później użyłem go, aby wykonać aktualizację oprogramowania dla wszystkich komputerów, kiedy CAT ipaddress.txt

0

Oto moja funkcja sterowania wątek:

#!/bin/bash 
# This function just checks jobs in background, don't do more things. 
# if jobs number is lower than MAX, then return to get more jobs; 
# if jobs number is greater or equal to MAX, then wait, until someone finished. 

# Usage: 
# thread_max 8 
# thread_max 0 # wait, until all jobs completed 

thread_max() { 
    local CHECK_INTERVAL="3s" 
    local CUR_THREADS= 
    local MAX= 
    [[ $1 ]] && MAX=$1 || return 127 

    # reset MAX value, 0 is easy to remember 
    [ $MAX -eq 0 ] && { 
     MAX=1 
     DEBUG "waiting for all tasks finish" 
    } 

    while true; do 
     CUR_THREADS=`jobs -p | wc -w` 

     # workaround about jobs bug. If don't execute it explicitily, 
     # CUR_THREADS will stick at 1, even no jobs running anymore. 
     jobs &>/dev/null 

     DEBUG "current thread amount: $CUR_THREADS" 
     if [ $CUR_THREADS -ge $MAX ]; then 
      sleep $CHECK_INTERVAL 
     else 
      return 0 
     fi 
    done 
} 
12

nie lubię za pomocą wait ponieważ pobiera zablokowane aż do wyjścia procesu, który nie jest idealny, gdy istnieje wielu proces czekać na jak mogę” t uzyskać aktualizację statusu do czasu zakończenia bieżącego procesu. Wolę używać do tego kombinacji kill -0 i sleep.

Przy oczekiwaniu na tablicę z pids używam poniższej funkcji waitPids(), aby uzyskać ciągłe informacje zwrotne na temat tego, które pidy wciąż oczekują na zakończenie.

declare -a pids 
waitPids() { 
    while [ ${#pids[@]} -ne 0 ]; do 
     echo "Waiting for pids: ${pids[@]}" 
     local range=$(eval echo {0..$((${#pids[@]}-1))}) 
     local i 
     for i in $range; do 
      if ! kill -0 ${pids[$i]} 2> /dev/null; then 
       echo "Done -- ${pids[$i]}" 
       unset pids[$i] 
      fi 
     done 
     pids=("${pids[@]}") # Expunge nulls created by unset. 
     sleep 1 
    done 
    echo "Done!" 
} 

Kiedy rozpocząć proces w tle, od razu dodać swój PID do tablicy pids stosując ten poniżej funkcji użyteczności:

addPid() { 
    desc=$1 
    pid=$2 
    echo "$desc -- $pid" 
    pids=(${pids[@]} $pid) 
} 

Oto przykład, który pokazuje, jak używać:

for i in {2..5}; do 
    sleep $i & 
    addPid "Sleep for $i" $! 
done 
waitPids 

A oto jak wygląda zwrotne:

Sleep for 2 -- 36271 
Sleep for 3 -- 36272 
Sleep for 4 -- 36273 
Sleep for 5 -- 36274 
Waiting for pids: 36271 36272 36273 36274 
Waiting for pids: 36271 36272 36273 36274 
Waiting for pids: 36271 36272 36273 36274 
Done -- 36271 
Waiting for pids: 36272 36273 36274 
Done -- 36272 
Waiting for pids: 36273 36274 
Done -- 36273 
Waiting for pids: 36274 
Done -- 36274 
Done! 
0

Podejście firmy haridsv jest wspaniałe, daje elastyczność w konfiguracji gniazd procesora, w których wiele procesów może być uruchomionych z nowymi zadaniami, które zostały zgłoszone jako zadania zakończone, utrzymując ogólne obciążenie. Oto moje modyfikacje kodu haridsv dla procesora n-slotowego dla "siatki" ngrid 'jobs' (używam go dla siatek modeli symulacyjnych) Następuje wynik testu dla 8 zadań 3 na raz, z bieżącymi sumami uruchomień , składane zakończona, a pozostałe wyjścia

#!/bin/bash 
######################################################################## 
# see haridsv on forking-multi-threaded-processes-bash 
# loop over grid, submitting jobs in the background. 
# As jobs complete new ones are set going to keep the number running 
# up to n as much as possible, until it tapers off at the end. 
# 
# 8 jobs 
ngrid=8 
# 3 at a time 
n=3 
# running counts 
running=0 
completed=0 
# previous values 
prunning=0 
pcompleted=0 
# 
######################################################################## 
# process monitoring functions 
# 
declare -a pids 
# 
function checkPids() { 
echo ${#pids[@]} 
if [ ${#pids[@]} -ne 0 ] 
then 
    echo "Checking for pids: ${pids[@]}" 
    local range=$(eval echo {0..$((${#pids[@]}-1))}) 
    local i 
    for i in $range; do 
     if ! kill -0 ${pids[$i]} 2> /dev/null; then 
      echo "Done -- ${pids[$i]}" 
      unset pids[$i] 
      completed=$(expr $completed + 1) 
     fi 
    done 
    pids=("${pids[@]}") # Expunge nulls created by unset. 
    running=$((${#pids[@]})) 
    echo "#PIDS :"$running 
fi 
} 
# 
function addPid() { 
    desc=$1 
    pid=$2 
    echo " ${desc} - "$pid 
    pids=(${pids[@]} $pid) 
} 
######################################################################## 
# 
# Loop and report when job changes happen, 
# keep going until all are completed. 
# 
idx=0 
while [ $completed -lt ${ngrid} ] 
do 
# 
    if [ $running -lt $n ] && [ $idx -lt ${ngrid} ] 
    then 
#################################################################### 
# 
# submit a new process if less than n 
# are running and we haven't finished... 
# 
# get desc for process 
# 
     name="job_"${idx} 
# background execution 
     sleep 3 & 
     addPid $name $! 
     idx=$(expr $idx + 1) 
# 
#################################################################### 
# 
    fi 
# 
    checkPids 
# if something changes... 
    if [ ${running} -gt ${prunning} ] || \ 
     [ ${completed} -gt ${pcompleted} ] 
    then 
     remain=$(expr $ngrid - $completed) 
     echo " Running: "${running}" Submitted: "${idx}\ 
       " Completed: "$completed" Remaining: "$remain 
    fi 
# save counts to prev values 
    prunning=${running} 
    pcompleted=${completed} 
# 
    sleep 1 
# 
done 
# 
######################################################################## 

test:

job_0 - 75257 
1 
Checking for pids: 75257 
#PIDS :1 
Running: 1 Submitted: 1 Completed: 0 Remaining: 8 
job_1 - 75262 
2 
Checking for pids: 75257 75262 
#PIDS :2 
Running: 2 Submitted: 2 Completed: 0 Remaining: 8 
job_2 - 75267 
3 
Checking for pids: 75257 75262 75267 
#PIDS :3 
Running: 3 Submitted: 3 Completed: 0 Remaining: 8 
3 
Checking for pids: 75257 75262 75267 
Done -- 75257 
#PIDS :2 
Running: 2 Submitted: 3 Completed: 1 Remaining: 7 
job_3 - 75277 
3 
Checking for pids: 75262 75267 75277 
Done -- 75262 
#PIDS :2 
Running: 2 Submitted: 4 Completed: 2 Remaining: 6 
job_4 - 75283 
3 
Checking for pids: 75267 75277 75283 
Done -- 75267 
#PIDS :2 
Running: 2 Submitted: 5 Completed: 3 Remaining: 5 
job_5 - 75289 
3 
Checking for pids: 75277 75283 75289 
#PIDS :3 
Running: 3 Submitted: 6 Completed: 3 Remaining: 5 
3 
Checking for pids: 75277 75283 75289 
Done -- 75277 
#PIDS :2 
Running: 2 Submitted: 6 Completed: 4 Remaining: 4 
job_6 - 75298 
3 
Checking for pids: 75283 75289 75298 
Done -- 75283 
#PIDS :2 
Running: 2 Submitted: 7 Completed: 5 Remaining: 3 
job_7 - 75304 
3 
Checking for pids: 75289 75298 75304 
Done -- 75289 
#PIDS :2 
Running: 2 Submitted: 8 Completed: 6 Remaining: 2 
2 
Checking for pids: 75298 75304 
#PIDS :2 
2 
Checking for pids: 75298 75304 
Done -- 75298 
#PIDS :1 
Running: 1 Submitted: 8 Completed: 7 Remaining: 1 
1 
Checking for pids: 75304 
Done -- 75304 
#PIDS :0 
Running: 0 Submitted: 8 Completed: 8 Remaining: 0