Bash FOR Loop


109

ฉันพยายามขนานสคริปต์ต่อไปนี้โดยเฉพาะอย่างยิ่งสำหรับอินสแตนซ์วนรอบทั้งสามของ FOR โดยใช้ GNU Parallel แต่ไม่สามารถทำได้ คำสั่ง 4 คำสั่งที่อยู่ในการวนซ้ำแบบ FOR ทำงานเป็นลำดับแต่ละลูปใช้เวลาประมาณ 10 นาที

#!/bin/bash

kar='KAR5'
runList='run2 run3 run4'
mkdir normFunc
for run in $runList
do 
  fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear

  rm -f *.mat
done

คำตอบ:


94

ทำไมคุณไม่แยกพวกเขา (พื้นหลัง aka)?

foo () {
    local run=$1
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

for run in $runList; do foo "$run" & done

ในกรณีที่ไม่ชัดเจนส่วนสำคัญอยู่ที่นี่:

for run in $runList; do foo "$run" & done
                                   ^

ทำให้ฟังก์ชั่นที่จะดำเนินการในเปลือกคดเคี้ยวในพื้นหลัง มันขนานกัน


6
ที่ทำงานเหมือนจับใจ ขอขอบคุณ. การใช้งานง่าย ๆ เช่นนี้ทำให้ฉันรู้สึกงี่เง่ามาก!
Ravnoor S Gill

8
ในกรณีที่ฉันมี 8 ไฟล์ให้ทำงานแบบขนาน แต่มีเพียง 4 คอร์ที่สามารถรวมเข้ากับการตั้งค่าหรือต้องใช้ Job Scheduler?
Ravnoor S Gill

6
มันไม่สำคัญสำหรับบริบทนี้ เป็นเรื่องปกติที่ระบบจะมีกระบวนการที่ใช้งานมากกว่าคอร์ หากคุณมีงานสั้น ๆ มากมายคุณควรป้อนคิวที่ให้บริการโดยจำนวนหรือเธรดผู้ปฏิบัติงาน <จำนวนคอร์ ฉันไม่รู้ว่าบ่อยแค่ไหนที่ทำกับการเขียนสคริปต์เชลล์ (ในกรณีนี้พวกเขาจะไม่เป็นเธรดพวกเขาจะเป็นกระบวนการอิสระ) แต่ด้วยงานที่ค่อนข้างน้อยมันจะไม่มีจุดหมาย เครื่องมือวางกำหนดการระบบปฏิบัติการจะดูแลสิ่งเหล่านี้
goldilocks

17
คุณอาจต้องการเพิ่มwaitคำสั่งในตอนท้ายเพื่อให้สคริปต์ต้นแบบไม่ออกจนกว่างานแบ็คกราวน์ทั้งหมดจะทำ
psusi

1
ฉันก็จะมีประโยชน์ที่จะ จำกัด จำนวนกระบวนการที่เกิดขึ้นพร้อมกัน: กระบวนการของฉันแต่ละกระบวนการใช้เวลา 100% ของคอร์เป็นเวลาประมาณ 25 นาที นี่คือเซิร์ฟเวอร์ที่ใช้ร่วมกันที่มี 16 คอร์ซึ่งผู้คนจำนวนมากกำลังทำงานอยู่ ฉันต้องเรียกใช้สคริปต์ 23 ชุด ถ้าฉันเรียกใช้พวกเขาทั้งหมดพร้อมกันแล้วฉันล้นเซิร์ฟเวอร์และทำให้มันไร้ประโยชน์สำหรับคนอื่นเป็นเวลาหนึ่งหรือสองชั่วโมง (โหลดไปถึง 30 ทุกอย่างอื่นช้าลง) ผมคิดว่ามันสามารถทำได้ด้วยniceแต่แล้วผมไม่ทราบว่ามันเคยจบ ..
naught101

150

งานตัวอย่าง

task(){
   sleep 0.5; echo "$1";
}

การรันตามลำดับ

for thing in a b c d e f g; do 
   task "$thing"
done

วิ่งแบบขนาน

for thing in a b c d e f g; do 
  task "$thing" &
done

รันแบบขนานในแบตช์ N กระบวนการ

N=4
(
for thing in a b c d e f g; do 
   ((i=i%N)); ((i++==0)) && wait
   task "$thing" & 
done
)

นอกจากนี้ยังเป็นไปได้ที่จะใช้ FIFOs เป็นเซมาฟอร์และใช้มันเพื่อให้แน่ใจว่ากระบวนการใหม่จะเกิดขึ้นเร็วที่สุดเท่าที่จะเป็นไปได้และไม่เกินกว่ากระบวนการ N ที่ทำงานในเวลาเดียวกัน แต่มันต้องใช้รหัสเพิ่มเติม

กระบวนการ N ที่มีสัญญาณที่ใช้ FIFO:

open_sem(){
    mkfifo pipe-$$
    exec 3<>pipe-$$
    rm pipe-$$
    local i=$1
    for((;i>0;i--)); do
        printf %s 000 >&3
    done
}
run_with_lock(){
    local x
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
     ( "$@"; )
    printf '%.3d' $? >&3
    )&
}

N=4
open_sem $N
for thing in {a..g}; do
    run_with_lock task $thing
done 

4
บรรทัดที่มีwaitอยู่ในนั้นช่วยให้กระบวนการทั้งหมดทำงานจนกว่าจะถึงnthกระบวนการแล้วรอให้กระบวนการอื่น ๆ ทั้งหมดเสร็จสิ้นใช่ไหม?
naught101

หากiเป็นศูนย์ให้รอสาย เพิ่มขึ้นiหลังจากการทดสอบที่ศูนย์
PSkocik

2
@ naught101 ใช่ waitไม่มี arg รอให้เด็กทุกคน นั่นทำให้สิ้นเปลืองเพียงเล็กน้อย วิธี-nt-ot
ไพพ์

1
@ BeowulfNode42 คุณไม่ต้องออก สถานะการส่งคืนของงานจะไม่ส่งผลกระทบต่อความสอดคล้องของเซมาฟอร์ตราบใดที่สถานะ (หรือบางอย่างที่มีความยาวเทลนั้น) ถูกเขียนกลับไปที่ฟีฟ่าหลังจากกระบวนการออกจากงาน / ขัดข้อง
PSkocik

1
FYI mkfifo pipe-$$คำสั่งต้องการเข้าถึงการเขียนที่เหมาะสมไปยังไดเร็กทอรีปัจจุบัน ดังนั้นฉันจึงต้องการระบุเส้นทางแบบเต็มเช่นเป็น/tmp/pipe-$$ไปได้ที่จะมีการเข้าถึงการเขียนสำหรับผู้ใช้ปัจจุบันมากกว่าที่จะใช้ไดเรกทอรีปัจจุบัน ใช่แทนที่ทั้งหมด 3 pipe-$$การเกิดขึ้นของ
BeowulfNode42

65
for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff

การทำงานจริงขึ้นอยู่กับคำสั่งของคุณหรือไม่ ฉันไม่คุ้นเคยกับพวกเขา rm *.matดูเล็กน้อยแนวโน้มที่จะเกิดความขัดแย้งถ้ามันจะทำงานในแบบคู่ขนาน ...


2
มันทำงานได้อย่างสมบูรณ์แบบเช่นกัน คุณถูกต้องฉันจะต้องเปลี่ยนrm *.matเป็นสิ่งที่ต้องการrm $run".mat"เพื่อให้มันทำงานได้โดยไม่ต้องมีกระบวนการหนึ่งรบกวนกระบวนการอื่น ขอบคุณค่ะ
Ravnoor S Gill

@RavnoorSGill ยินดีต้อนรับสู่ Stack Exchange! หากคำตอบนี้แก้ไขปัญหาของคุณได้โปรดทำเครื่องหมายว่ายอมรับแล้วทำเครื่องหมายถูกที่อยู่ด้านข้าง
Gilles

7
+1 waitซึ่งฉันลืมไป
goldilocks

5
หากมี 'สิ่ง' มากมายสิ่งนี้จะไม่เริ่มต้นกระบวนการมากมายใช่ไหม มันจะเป็นการดีกว่าถ้าคุณเริ่มต้นกระบวนการที่มีสติอย่างเดียวพร้อมกันใช่ไหม
David Doria

1
เคล็ดลับที่เป็นประโยชน์มาก! จะตั้งค่าจำนวนเธรดในกรณีนี้ได้อย่างไร?
Dadong Zhang

30
for stuff in things
do
sem -j+0 ( something
  with
  stuff )
done
sem --wait

สิ่งนี้จะใช้ semaphores โดยทำซ้ำให้มากที่สุดเท่าจำนวนคอร์ที่มีอยู่ (-j +0 หมายความว่าคุณจะทำขนานงานN + 0โดยที่N คือจำนวนคอร์ที่มีอยู่ )

sem - รอบอกให้รอจนกว่าการวนซ้ำทั้งหมดในลูปจะสิ้นสุดการทำงานก่อนที่จะรันบรรทัดต่อเนื่องของรหัส

หมายเหตุ: คุณจะต้องใช้ "ขนาน" จากโครงการขนาน GNU (sudo apt-get install ขนาน)


1
เป็นไปได้ที่จะผ่าน 60 ฉันโยนข้อผิดพลาดที่บอกว่าตัวบอกไฟล์ไม่เพียงพอ
chovy

หากนี่คือการโยนข้อผิดพลาดทางไวยากรณ์เพราะวงเล็บสำหรับทุกคนเกินไปดูที่คำตอบโดย moritzschaefer
Nicolai

10

วิธีง่าย ๆ ที่ฉันใช้บ่อย ๆ :

cat "args" | xargs -P $NUM_PARALLEL command

สิ่งนี้จะเรียกใช้คำสั่งส่งผ่านแต่ละบรรทัดของไฟล์ "args" ขนานกันโดยรันที่ $ NUM_PARALLEL มากที่สุดในเวลาเดียวกัน

คุณยังสามารถดูตัวเลือก -I สำหรับ xargs หากคุณต้องการแทนที่อาร์กิวเมนต์อินพุตในสถานที่ต่างกัน


6

ดูเหมือนว่างาน fsl จะขึ้นอยู่กับแต่ละคนดังนั้นงาน 4 งานไม่สามารถทำงานแบบขนาน อย่างไรก็ตามการวิ่งสามารถวิ่งขนานกันได้

ทำให้ฟังก์ชั่นทุบตีทำงานครั้งเดียวและเรียกใช้ฟังก์ชันนั้นแบบขนาน:

#!/bin/bash

myfunc() {
    run=$1
    kar='KAR5'
    mkdir normFunc
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

export -f myfunc
parallel myfunc ::: run2 run3 run4

หากต้องการเรียนรู้เพิ่มเติมดูวิดีโอแนะนำ: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1และใช้เวลาหนึ่งชั่วโมงในการเดินกวดวิชาhttp://www.gnu.org/software/parallel/parallel_tutorial.htmlคำสั่งของคุณ สายจะรักคุณมัน


หากคุณใช้เชลล์ที่ไม่มีการทุบตีคุณจะต้องดำเนินการexport SHELL=/bin/bashก่อนที่จะทำงานแบบขนาน มิฉะนั้นคุณจะได้รับข้อผิดพลาดเช่น:Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey: นั่นไม่ใช่สิ่งที่ Shebang มีไว้เพื่ออะไร?
naught101

5

การประมวลผลแบบขนานในกระบวนการสูงสุด N กระบวนการพร้อมกัน

#!/bin/bash

N=4

for i in {a..z}; do
    (
        # .. do your stuff here
        echo "starting task $i.."
        sleep $(( (RANDOM % 3) + 1))
    ) &

    # allow only to execute $N jobs in parallel
    if [[ $(jobs -r -p | wc -l) -gt $N ]]; then
        # wait only for first job
        wait -n
    fi

done

# wait for pending jobs
wait

echo "all done"

3

ฉันชอบคำตอบจาก @lev เพราะให้การควบคุมจำนวนกระบวนการสูงสุดในวิธีที่ง่ายมาก อย่างไรก็ตามตามที่อธิบายไว้ในคู่มือ sem ไม่ทำงานกับวงเล็บ

for stuff in things
do
sem -j +0 "something; \
  with; \
  stuff"
done
sem --wait

ทำงานหรือไม่

-j + N เพิ่ม N ให้กับจำนวนแกน CPU วิ่งไปหางานจำนวนมากนี้ในแบบคู่ขนาน สำหรับการคำนวณอย่างเข้มข้นงาน -j +0 นั้นมีประโยชน์เพราะมันจะทำงานจำนวน number of of cpu-cores พร้อมกัน

-j -N ลบ N จากจำนวนแกนประมวลผล CPU วิ่งไปหางานจำนวนมากนี้ในแบบคู่ขนาน หากจำนวนที่ประเมินน้อยกว่า 1 จะใช้ 1 ดูเพิ่มเติม --use-cpus-แทนที่จะ-of-cores


1

ในกรณีของฉันฉันไม่สามารถใช้สัญญาณ (ฉันใช้ git-bash บน Windows) ดังนั้นฉันจึงหาวิธีทั่วไปในการแบ่งงานระหว่างคนงาน N ก่อนที่จะเริ่ม

มันทำงานได้ดีหากงานใช้เวลาประมาณเท่ากัน ข้อเสียคือถ้าคนงานคนหนึ่งใช้เวลานานในการทำงานส่วนอื่น ๆ ที่ทำไปแล้วจะไม่ช่วย

แบ่งงานระหว่าง N คนงาน (1 ต่อหลัก)

# array of assets, assuming at least 1 item exists
listAssets=( {a..z} ) # example: a b c d .. z
# listAssets=( ~/"path with spaces/"*.txt ) # could be file paths

# replace with your task
task() { # $1 = idWorker, $2 = asset
  echo "Worker $1: Asset '$2' START!"
  # simulating a task that randomly takes 3-6 seconds
  sleep $(( ($RANDOM % 4) + 3 ))
  echo "    Worker $1: Asset '$2' OK!"
}

nVirtualCores=$(nproc --all)
nWorkers=$(( $nVirtualCores * 1 )) # I want 1 process per core

worker() { # $1 = idWorker
  echo "Worker $1 GO!"
  idAsset=0
  for asset in "${listAssets[@]}"; do
    # split assets among workers (using modulo); each worker will go through
    # the list and select the asset only if it belongs to that worker
    (( idAsset % nWorkers == $1 )) && task $1 "$asset"
    (( idAsset++ ))
  done
  echo "    Worker $1 ALL DONE!"
}

for (( idWorker=0; idWorker<nWorkers; idWorker++ )); do
  # start workers in parallel, use 1 process for each
  worker $idWorker &
done
wait # until all workers are done

0

ฉันมีปัญหากับ@PSkocikทางออกของ ระบบของฉันไม่มี GNU Parallel ให้ใช้งานเป็นแพ็คเกจและsemมีข้อยกเว้นเมื่อฉันสร้างและรันด้วยตนเอง จากนั้นฉันก็ลองตัวอย่างสัญญาณแบบ FIFO เช่นกันซึ่งทำให้เกิดข้อผิดพลาดอื่น ๆ เกี่ยวกับการสื่อสาร

@eyeApps xargs ที่แนะนำ แต่ฉันไม่รู้วิธีทำให้มันใช้งานได้กับกรณีการใช้งานที่ซับซ้อนของฉัน (ยินดีต้อนรับตัวอย่าง)

นี่คือโซลูชันของฉันสำหรับงานแบบขนานซึ่งประมวลผลNงานได้ครั้งละมากตามที่กำหนดค่าโดย_jobs_set_max_parallel:

_lib_jobs.sh:

function _jobs_get_count_e {
   jobs -r | wc -l | tr -d " "
}

function _jobs_set_max_parallel {
   g_jobs_max_jobs=$1
}

function _jobs_get_max_parallel_e {
   [[ $g_jobs_max_jobs ]] && {
      echo $g_jobs_max_jobs

      echo 0
   }

   echo 1
}

function _jobs_is_parallel_available_r() {
   (( $(_jobs_get_count_e) < $g_jobs_max_jobs )) &&
      return 0

   return 1
}

function _jobs_wait_parallel() {
   # Sleep between available jobs
   while true; do
      _jobs_is_parallel_available_r &&
         break

      sleep 0.1s
   done
}

function _jobs_wait() {
   wait
}

ตัวอย่างการใช้งาน:

#!/bin/bash

source "_lib_jobs.sh"

_jobs_set_max_parallel 3

# Run 10 jobs in parallel with varying amounts of work
for a in {1..10}; do
   _jobs_wait_parallel

   # Sleep between 1-2 seconds to simulate busy work
   sleep_delay=$(echo "scale=1; $(shuf -i 10-20 -n 1)/10" | bc -l)

   ( ### ASYNC
   echo $a
   sleep ${sleep_delay}s
   ) &
done

# Visualize jobs
while true; do
   n_jobs=$(_jobs_get_count_e)

   [[ $n_jobs = 0 ]] &&
      break

   sleep 0.1s
done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.