Kommandoer og enkel parallellisering

Richard Tingstad

Si at du skal kjøre en kommando for veldig mange filer eller inputlinjer.

Sekvensiell kjøring (én etter én) tar lang tid, og samtidighet i databehandling er kjent for å være vanskelig?

Det finnes heldigvis noen enkle mekanismer vi kan bruke.

Pipeline

Har du tenkt på at en pipe er et eksempel på samtidig eksekvering?

gunzip --to-stdout large.gz | grep Foo | head

Her vil programmene gunzip, grep og head kjøre som separate prosesser som (implisitt) samarbeider. Venstresiden vil ikke prosessere hele datamengden før den sendes videre. Prosessene startes samtidig og skriver og leser fra et mellomlager (buffer) av en viss størrelse, gitt av operativsystemet. Lesere venter når inputdata ikke er tilgjengelig (mellomlageret er tomt), og skrivere venter når mellomlageret er fullt. Slik kan partene prosessere store mengder data i egen hastighet uten problemer. Og du slipper å gå tom for minne eller diskplass!

Asynkrone jobber

Hvis du har en begrenset mengde input kan du kjøre alle samtidig i bakgrunnen med &:

cat urls.txt | while read url; do
    curl -sO "$url" &
done
wait # venter til alle er ferdig

Men hvis det er mange vil du kanskje bare kjøre opptil N stk. om gangen, og da kan xargs -P være en reddende engel!

xargs -P

xargs leser inputdata og eksekverer kommando med inputdataen som parametre. Følgende vil kjøre sekvensielt:

cat urls.txt | xargs -n1 curl -sO

-n 1 betyr maksimalt ett input-argument per prosess (så echo a b | xargs rm gir rm a b, mens echo a b | xargs -n1 rm gir rm a; rm b).

Med -P kan vi parallellisere: -P 0 vil vi gjenskape while-loopen over med én prosess per url; -P 8 vil kjøre opptil 8 prosesser i parallell.

cat IDs.txt | xargs -n1 -P8 -IX curl https://service/id/X -s

Her brukes X som placeholder siden argumentet ikke er frittstående og til slutt.

Hvis du trenger en mer komplisert kommando er det enkleste å lagre den i en fil og kjøre cat IDs.txt | xargs -n1 -P8 sh fil.sh, der fil.sh inneholder f.eks:

#!/bin/sh
set -e

curl "https://service/id/$1" | grep Foo > res/"$1"

Det er mulig å implementere sin egen "process pool", men xargs -P er minst like enkelt.

Lykke til!

P.S. xargs -P er ikke en standardisert option, men godt utbredt.

P.P.S. Hvis du lurer på hvordan første kommando i pipeline-en avsluttes så skjer det når den forsøker å skrive etter at lese-prosessen er avsluttet — den vil da motta signalet SIGPIPE fra operativsystemet og terminere.