Laten we zeggen dat ik een lus in Bash heb:
for foo in `some-command`
do
do-something $foo
done
do-something
is cpu-gebonden en ik heb een mooie glanzende 4-coreprocessor. Ik wil graag tot 4 do-something
‘s tegelijk kunnen uitvoeren.
De naïeve benadering lijkt te zijn:
for foo in `some-command`
do
do-something $foo &
done
Hiermee worden alledo-something
s tegelijk uitgevoerd, maar er zijn een paar nadelen, voornamelijk dat doe-iets ook een aantal significante I/O kan hebben die allemaaltegelijk kan wat langzamer gaan. Het andere probleem is dat dit codeblok onmiddellijk terugkeert, dus er is geen manier om ander werk te doen als alle do-something
‘s klaar zijn.
Hoe zou je deze lus schrijven, zodat er altijd X do-something
‘s tegelijk worden uitgevoerd?
Antwoord 1, autoriteit 100%
Afhankelijk van wat u wilt doen, kan xargs ook helpen (hier: documenten converteren met pdf2ps):
cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )
find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus pdf2ps
Uit de documenten:
--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.
Antwoord 2, autoriteit 59%
Met GNU Parallel http://www.gnu.org/software/parallel/je kunt schrijven:
some-command | parallel do-something
GNU Parallel ondersteunt ook het uitvoeren van taken op externe computers. Dit zal één per CPU-kern uitvoeren op de externe computers – zelfs als ze een verschillend aantal kernen hebben:
some-command | parallel -S server1,server2 do-something
Een meer geavanceerd voorbeeld: hier geven we een lijst met bestanden waarvan we willen dat my_script wordt uitgevoerd. Bestanden hebben de extensie (misschien .jpeg). We willen dat de uitvoer van my_script naast de bestanden in basename.out wordt geplaatst (bijv. foo.jpeg -> foo.out). We willen my_script één keer uitvoeren voor elke kern die de computer heeft en we willen het ook op de lokale computer uitvoeren. Voor de externe computers willen we dat het bestand dat wordt verwerkt naar de opgegeven computer wordt overgebracht. Als my_script klaar is, willen we dat foo.out terug wordt overgedragen en dat foo.jpeg en foo.out worden verwijderd van de externe computer:
cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"
GNU Parallel zorgt ervoor dat de uitvoer van elke taak niet gemengd is, dus u kunt de uitvoer gebruiken als invoer voor een ander programma:
some-command | parallel do-something | postprocess
Bekijk de video’s voor meer voorbeelden: https://www.youtube.com/playlist?list =PL284C9FF2488BC6D1
Antwoord 3, autoriteit 32%
maxjobs=4 parallelliseren () { terwijl [ $# -gt 0]; doen jobcnt=(`banen -p`) if [ ${#jobcnt[@]} -lt $maxjobs]; dan iets doen $1 & verschuiving anders slapen 1 fi gedaan wachten } parallel arg1 arg2 "5 args naar derde taak" arg4 ...
Antwoord 4, autoriteit 25%
Hier een alternatieve oplossing die in .bashrc kan worden ingevoegd en voor alledaagse one-liners kan worden gebruikt:
function pwait() {
while [ $(jobs -p | wc -l) -ge $1 ]; do
sleep 1
done
}
Om het te gebruiken, hoef je alleen maar &
na de jobs en een pwait-aanroep te plaatsen, de parameter geeft het aantal parallelle processen aan:
for i in *; do
do_something $i &
pwait 10
done
Het zou leuker zijn om wait
te gebruiken in plaats van bezig te wachten op de output van jobs -p
, maar er lijkt geen voor de hand liggende oplossing te zijn om te wachten tot een van de opgegeven taken is voltooid in plaats van ze allemaal.
Antwoord 5, autoriteit 16%
Gebruik in plaats van een gewone bash een Makefile en specificeer vervolgens het aantal gelijktijdige taken met make -jX
waarbij X het aantal taken is dat tegelijk moet worden uitgevoerd.
Of u kunt wait
(“man wait
“) gebruiken: start verschillende onderliggende processen, bel wait
– het wordt afgesloten wanneer het kind processen eindigen.
maxjobs = 10
foreach line in `cat file.txt` {
jobsrunning = 0
while jobsrunning < maxjobs {
do job &
jobsrunning += 1
}
wait
}
job ( ){
...
}
Als u het resultaat van de taak moet opslaan, wijs het resultaat dan toe aan een variabele. Na wait
controleer je gewoon wat de variabele bevat.
Antwoord 6, autoriteit 12%
Misschien een parallellisatieprogramma proberen in plaats van de lus te herschrijven? Ik ben een grote fan van xjobs. Ik gebruik de hele tijd xjobs om massaal bestanden over ons netwerk te kopiëren, meestal bij het opzetten van een nieuwe databaseserver.
http://www.maier-komor.de/xjobs.html
Antwoord 7, autoriteit 10%
Als je bekend bent met het make
commando, kun je de lijst met commando’s die je wilt uitvoeren meestal als een makefile uitdrukken. Als u bijvoorbeeld $SOME_COMMAND moet uitvoeren op bestanden *.input die elk *.output produceren, kunt u de makefile
gebruiken
INPUT = a.input b.input UITGANG = $(INPUT:.input=.output) %.uitvoer invoer $(SOME_COMMAND) $< [email protected] alles: $(OUTPUT)
en dan gewoon rennen
make -j<NUMBER>
om maximaal NUMBER opdrachten parallel uit te voeren.
Antwoord 8, autoriteit 9%
Hoewel het waarschijnlijk onmogelijk is om dit goed te doen in bash
, kun je redelijk eenvoudig een semi-rechts doen. bstark
gaf een redelijke benadering van het recht, maar hij heeft de volgende gebreken:
- Woorden splitsen: u kunt er geen taken aan doorgeven die een van de volgende tekens in hun argumenten gebruiken: spaties, tabs, nieuwe regels, sterren, vraagtekens. Als je dat doet, gaan dingen kapot, mogelijk onverwachts.
- Het is afhankelijk van de rest van je script om niets op de achtergrond te zetten. Als je dat doet, of als je later iets toevoegt aan het script dat op de achtergrond wordt verzonden omdat je bent vergeten dat je geen achtergrondtaken mag gebruiken vanwege zijn fragment, gaan de dingen kapot.
Een andere benadering die deze gebreken niet heeft, is de volgende:
scheduleAll() {
local job i=0 max=4 pids=()
for job; do
(( ++i % max == 0 )) && {
wait "${pids[@]}"
pids=()
}
bash -c "$job" & pids+=("$!")
done
wait "${pids[@]}"
}
Merk op dat deze eenvoudig kan worden aangepast om ook de afsluitcode van elke taak te controleren wanneer deze eindigt, zodat u de gebruiker kunt waarschuwen als een taak mislukt of een afsluitcode kunt instellen voor scheduleAll
op basis van het aantal van banen die zijn mislukt, of zoiets.
Het probleem met deze code is precies dat:
- Het plant vier (in dit geval) taken tegelijk en wacht dan tot alle vier zijn afgelopen. Sommige zijn misschien eerder klaar dan andere, waardoor de volgende batch van vier taken moet wachten tot de langste van de vorige batch is voltooid.
Een oplossing die dit laatste probleem oplost, zou kill -0
moeten gebruiken om te peilen of een van de processen is verdwenen in plaats van wait
en de volgende te plannen functie. Dat introduceert echter een klein nieuw probleem: je hebt een race-conditie tussen het beëindigen van een taak en de kill -0
die controleert of deze is beëindigd. Als de taak is beëindigd en tegelijkertijd een ander proces op uw systeem opstart, waarbij een willekeurige PID wordt genomen die toevallig die van de zojuist voltooide taak is, zal de kill -0
uw taak niet opmerken als het klaar is, gaan de dingen weer kapot.
Een perfecte oplossing is niet mogelijk in bash
.
Antwoord 9, autoriteit 4%
functie voor bash:
parallel ()
{
awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\[email protected]\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make [email protected] -f - all
}
gebruik:
cat my_commands | parallel -j 4
Antwoord 10, autoriteit 3%
Het project waar ik aan werk gebruikt het waitcommando om parallelle shell (ksh eigenlijk) processen te besturen. Om uw zorgen over IO weg te nemen, op een modern besturingssysteem, is het mogelijk dat parallelle uitvoering de efficiëntie daadwerkelijk zal verhogen. Als alle processen dezelfde blokken op schijf lezen, hoeft alleen het eerste proces de fysieke hardware te raken. De andere processen kunnen het blok vaak uit de schijfcache van het besturingssysteem in het geheugen halen. Het is duidelijk dat het lezen uit het geheugen een aantal orden van grootte sneller is dan het lezen van een schijf. Het voordeel vereist ook geen coderingswijzigingen.
Antwoord 11, autoriteit 3%
Echtlaat op het feest hier, maar hier is een andere oplossing.
Veel oplossingen verwerken geen spaties/speciale tekens in de commando’s, houden N jobs niet altijd actief, eten cpu in drukke lussen of vertrouwen op externe afhankelijkheden (bijv. GNU parallel
).
Met inspiratie voor de verwerking van dead/zombie-processenis hier een pure bash-oplossing:
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
}
En voorbeeldgebruik:
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[@]}"
De uitvoer:
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
Voor uitvoerverwerking per proces kan $$
worden gebruikt om in te loggen op een bestand, bijvoorbeeld:
function job_done {
cat "$1.log"
}
cmds=( \
"echo 1 \$\$ >\$\$.log" \
"echo 2 \$\$ >\$\$.log" \
)
run_parallel_jobs 2 "job_done" "${cmds[@]}"
Uitvoer:
1 56871
2 56872
Antwoord 12
$DOMAINS = “lijst van een aantal domeinen in commando’s”
voor foo in some-command
doen
eval `some-command for $DOMAINS` &
job[$i]=$!
i=$(( i + 1))
klaar
Ndomains=echo $DOMAINS |wc -w
voor i in $(seq 1 1 $Ndomains)
doen
echo “wacht op ${job[$i]}”
wacht “${job[$i]}”
klaar
in dit concept zal werken voor de parallellisatie. het belangrijkste is dat de laatste regel van de evaluatie ‘&’ is
die de commando’s op de achtergrond plaatst.