Song song một Bash FOR Loop


109

Tôi đã cố gắng song song hóa tập lệnh sau, cụ thể là từng trường hợp trong ba trường hợp vòng lặp FOR, sử dụng GNU Parallel nhưng không thể thực hiện được. 4 lệnh chứa trong vòng lặp FOR chạy theo chuỗi, mỗi vòng lặp mất khoảng 10 phút.

#!/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

Câu trả lời:


94

Tại sao bạn không chỉ rẽ nhánh (còn gọi là nền) chúng?

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

Trong trường hợp không rõ ràng, phần quan trọng là ở đây:

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

Làm cho hàm được thực thi trong một shell fork trong nền. Đó là song song.


6
Tiếng Anh> Tiếng Việt. Cảm ơn bạn. Cách thực hiện đơn giản như vậy (Làm cho tôi cảm thấy rất ngu ngốc bây giờ!).
Ravnoor S Gill

8
Trong trường hợp tôi có 8 tệp để chạy song song nhưng chỉ có 4 lõi, điều đó có thể được tích hợp trong một cài đặt như vậy không hoặc có yêu cầu Bộ lập lịch công việc không?
Ravnoor S Gill

6
Nó không thực sự quan trọng trong bối cảnh này; Hệ thống có quá trình hoạt động nhiều hơn lõi là bình thường. Nếu bạn có nhiều tác vụ ngắn , lý tưởng nhất là bạn sẽ cung cấp một hàng đợi được phục vụ bởi một số hoặc các luồng công nhân <số lượng lõi. Tôi không biết mức độ thường xuyên được thực hiện với shell script (trong trường hợp đó, chúng sẽ không phải là luồng, chúng sẽ là các tiến trình độc lập) nhưng với một vài nhiệm vụ dài thì sẽ là vô nghĩa. Bộ lập lịch hệ điều hành sẽ chăm sóc chúng.
goldilocks

17
Bạn cũng có thể muốn thêm một waitlệnh ở cuối để tập lệnh chính không thoát cho đến khi tất cả các công việc nền làm.
psusi

1
Tôi cũng sẽ rất hữu ích khi giới hạn số lượng quy trình đồng thời: mỗi quy trình của tôi sử dụng 100% thời gian của lõi trong khoảng 25 phút. Đây là trên một máy chủ được chia sẻ với 16 lõi, nơi có nhiều người đang chạy việc. Tôi cần chạy 23 bản sao của kịch bản. Nếu tôi chạy tất cả chúng đồng thời, thì tôi quét máy chủ và làm cho nó trở nên vô dụng đối với những người khác trong một hoặc hai giờ (tải lên đến 30, mọi thứ khác chậm lại). Tôi đoán nó có thể được thực hiện với nice, nhưng sau đó tôi không biết liệu nó có bao giờ kết thúc không ..
naught 101

150

Nhiệm vụ mẫu

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

Chạy tuần tự

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

Chạy song song

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

Chạy song song trong các lô N-process

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

Bạn cũng có thể sử dụng FIFO làm semaphores và sử dụng chúng để đảm bảo rằng các quy trình mới được sinh ra càng sớm càng tốt và không có nhiều hơn N quy trình chạy cùng một lúc. Nhưng nó đòi hỏi nhiều mã hơn.

N xử lý với một semaphore dựa trê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
Dòng với waitnó về cơ bản cho phép tất cả các quy trình chạy, cho đến khi nó đạt được nthquy trình, sau đó chờ cho tất cả các quy trình khác chạy xong, đúng không?
ness101

Nếu ibằng 0, hãy gọi chờ. Tăng isau khi kiểm tra không.
PSkocik

2
@ ness101 Có. waitw / không arg chờ đợi cho tất cả trẻ em. Điều đó làm cho nó một chút lãng phí. Cách tiếp cận semaphore dựa trên đường ống cung cấp cho bạn sự đồng thời trôi chảy hơn (Tôi đã sử dụng nó trong một hệ thống xây dựng dựa trên vỏ tùy chỉnh cùng với -nt/ -otkiểm tra thành công trong một thời gian bây giờ)
PSkocik

1
@ BeowulfNode42 Bạn không cần phải thoát. Trạng thái hoàn trả của nhiệm vụ sẽ không gây hại cho tính nhất quán của semaphore miễn là trạng thái (hoặc một cái gì đó có chiều dài đó) được viết lại cho fifo sau khi quá trình của nhiệm vụ thoát / gặp sự cố.
PSkocik

1
FYI mkfifo pipe-$$lệnh cần truy cập ghi thích hợp vào thư mục hiện tại. Vì vậy, tôi thích chỉ định đường dẫn đầy đủ /tmp/pipe-$$như rất có thể có quyền truy cập ghi có sẵn cho người dùng hiện tại thay vì dựa vào bất cứ thư mục hiện tại nào. Có thay thế cả 3 lần pipe-$$.
BeowulfNode42

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

Cho dù nó thực sự hoạt động phụ thuộc vào các lệnh của bạn; Tôi không quen thuộc với họ. Các rm *.mattrông hơi dễ bị xung đột nếu nó chạy song song ...


2
Điều này chạy hoàn hảo là tốt. Bạn nói đúng, tôi sẽ phải thay đổi rm *.matthành một cái gì đó rm $run".mat"để làm cho nó hoạt động mà không cần một quá trình can thiệp vào quá trình khác. Cảm ơn bạn .
Ravnoor S Gill

@RavnoorSGill Chào mừng bạn đến với Stack Exchange! Nếu câu trả lời này giải quyết vấn đề của bạn, vui lòng đánh dấu nó là được chấp nhận bằng cách đánh dấu vào dấu kiểm bên cạnh nó.
Gilles

7
+1 cho wait, mà tôi quên mất.
goldilocks

5
Nếu có hàng tấn 'thứ', liệu điều này có bắt đầu hàng tấn quy trình không? Sẽ tốt hơn nếu chỉ bắt đầu một số lượng quá trình lành mạnh đồng thời, phải không?
David Doria

1
Mẹo rất hữu ích! Làm thế nào để thiết lập số lượng chủ đề trong trường hợp này?
Dadong Zhang

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

Điều này sẽ sử dụng semaphores, song song nhiều lần lặp như số lượng lõi có sẵn (-j +0 có nghĩa là bạn sẽ song song hóa các công việc N + 0 , trong đó N là số lượng lõi có sẵn ).

sem --wait nói chờ cho đến khi tất cả các lần lặp trong vòng lặp for đã chấm dứt thực thi trước khi thực hiện các dòng mã liên tiếp.

Lưu ý: bạn sẽ cần "song song" từ dự án song song GNU (sudo apt-get install song song).


1
Có thể đi qua 60? Của tôi ném một lỗi nói không đủ mô tả tập tin.
chovy

Nếu điều này gây ra lỗi cú pháp vì quá niềng răng cho bất cứ ai, hãy xem câu trả lời của moritzschaefer.
Nicolai

10

Một cách thực sự dễ dàng mà tôi thường sử dụng:

cat "args" | xargs -P $NUM_PARALLEL command

Điều này sẽ chạy lệnh, chuyển qua từng dòng của tệp "args", song song, chạy tối đa $ NUM_PARALLEL cùng một lúc.

Bạn cũng có thể xem xét tùy chọn -I cho xargs, nếu bạn cần thay thế các đối số đầu vào ở các vị trí khác nhau.


6

Có vẻ như các công việc fsl phụ thuộc vào nhau, vì vậy 4 công việc không thể chạy song song. Việc chạy, tuy nhiên, có thể được chạy song song.

Tạo một hàm bash chạy một lần chạy và chạy hàm đó song song:

#!/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

Để tìm hiểu thêm, hãy xem các video giới thiệu: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 và dành một giờ đi bộ qua hướng dẫn http://www.gnu.org/software/abul/abul_tutorial.html Lệnh của bạn dòng sẽ yêu bạn cho nó.


Nếu bạn đang sử dụng shell không bash, bạn cũng cần phải export SHELL=/bin/bashchạy trước khi chạy song song. Nếu không, bạn sẽ gặp một lỗi như:Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey: không phải đó là những gì shebang dành cho?
ness101

5

Thực hiện song song trong đồng thời quá trình N tối đa

#!/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

Tôi thực sự thích câu trả lời từ @lev vì nó cung cấp quyền kiểm soát số lượng quy trình tối đa một cách rất đơn giản. Tuy nhiên như được mô tả trong hướng dẫn , sem không hoạt động với dấu ngoặc.

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

Làm công việc.

-j + N Thêm N vào số lõi CPU. Chạy đến nhiều công việc này song song. Đối với các công việc chuyên sâu tính toán -j +0 rất hữu ích vì nó sẽ chạy các công việc số lượng lõi cùng một lúc.

-j -N Trừ N từ số lõi CPU. Chạy đến nhiều công việc này song song. Nếu số lượng đánh giá nhỏ hơn 1 thì 1 sẽ được sử dụng. Xem thêm - sử dụng-cpus-thay vì lõi.


1

Trong trường hợp của tôi, tôi không thể sử dụng semaphore (tôi đang sử dụng git-bash trên Windows), vì vậy tôi đã nghĩ ra một cách chung để phân chia nhiệm vụ giữa N worker, trước khi chúng bắt đầu.

Nó hoạt động tốt nếu các nhiệm vụ mất khoảng thời gian như nhau. Nhược điểm là, nếu một trong những công nhân mất nhiều thời gian để thực hiện phần công việc của mình, thì những người khác đã hoàn thành sẽ không giúp được gì.

Chia công việc giữa N công nhân (1 mỗi lõi)

# 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

Tôi gặp rắc rối với @PSkocikgiải pháp của. Hệ thống của tôi không có sẵn GNU Parallel dưới dạng gói và semđã tạo một ngoại lệ khi tôi xây dựng và chạy nó bằng tay. Sau đó, tôi đã thử ví dụ semaphore FIFO cũng đã đưa ra một số lỗi khác liên quan đến giao tiếp.

@eyeApps xargs đề xuất nhưng tôi không biết làm thế nào để nó hoạt động với trường hợp sử dụng phức tạp của mình (ví dụ sẽ được chào đón).

Đây là giải pháp của tôi cho các công việc song song xử lý các Ncông việc tại một thời điểm như được định cấu hình bởi _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
}

Ví dụ sử dụng:

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.