Câu hỏi Parallelize Bash script với số lượng tối đa các quy trình


Cho phép nói rằng tôi có một vòng lặp trong Bash:

for foo in `some-command`
do
   do-something $foo
done

do-something là cpu bị ràng buộc và tôi có một bộ xử lý 4 lõi sáng bóng. Tôi muốn có thể chạy tới 4 do-somethingcùng một lúc.

Cách tiếp cận ngây thơ dường như là:

for foo in `some-command`
do
   do-something $foo &
done

Điều này sẽ chạy tất cả các  do-somethingcùng một lúc, nhưng có một vài nhược điểm, chủ yếu là do-một cái gì đó cũng có thể có một số I / O quan trọng thực hiện tất cả các cùng một lúc có thể làm chậm một chút. Vấn đề khác là khối mã này trả về ngay lập tức, vì vậy không có cách nào để thực hiện công việc khác khi tất cả do-somethings được hoàn thành.

Làm thế nào bạn sẽ viết vòng lặp này để luôn có X do-somethings chạy cùng một lúc?


76
2017-09-01 16:47


gốc


Là một sidenode, tôi đã mơ ước thêm tùy chọn make -j vào bash cho nguyên thủy. Nó sẽ không làm việc luôn, nhưng đối với một số trường hợp đơn giản, nơi bạn biết cơ thể của vòng lặp sẽ làm điều gì đó độc đáo cho mỗi lần lặp lại, nó sẽ khá sạch sẽ khi chỉ nói "for -j 4 ...". - unwind
Tham chiếu chéo tới stackoverflow.com/questions/1537956/… cho một giải pháp bash giúp giảm thiểu các vấn đề về hiệu suất và cho phép các nhóm các tiến trình con, được giữ riêng biệt. - paxdiablo
Tôi muốn giới thiệu giải pháp của tôi stackoverflow.com/a/28965927/340581 - Tuttle


Các câu trả lời:


Tùy thuộc vào những gì bạn muốn làm xargs cũng có thể giúp (ở đây: chuyển đổi tài liệu với pdf2ps):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

Từ tài liệu:

--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.

52
2018-05-19 07:50



Phương pháp này, theo ý kiến ​​của tôi, là giải pháp thanh lịch nhất. Ngoại trừ, vì tôi hoang tưởng, tôi luôn thích sử dụng find [...] -print0 và xargs -0. - amphetamachine
cpus=$(getconf _NPROCESSORS_ONLN) - mr.spuratic
Từ hướng dẫn sử dụng, tại sao không sử dụng --max-procs=0 để có được nhiều quy trình nhất có thể? - EverythingRightPlace
@EverythingRightPlace, câu hỏi một cách rõ ràng yêu cầu không có nhiều quy trình hơn các bộ xử lý có sẵn. --max-procs=0 giống như nỗ lực của người hỏi (bắt đầu với nhiều quy trình làm đối số). - Toby Speight


Với GNU Parallel http://www.gnu.org/software/parallel/ bạn có thể viết:

some-command | parallel do-something

GNU Parallel cũng hỗ trợ các công việc đang chạy trên các máy tính từ xa. Điều này sẽ chạy một lõi trên mỗi CPU trên các máy tính từ xa - ngay cả khi chúng có số lõi khác nhau:

some-command | parallel -S server1,server2 do-something

Một ví dụ nâng cao hơn: Ở đây chúng tôi liệt kê các tệp mà chúng tôi muốn my_script chạy trên đó. Các tệp có phần mở rộng (có thể là .jpeg). Chúng tôi muốn đầu ra của my_script được đặt bên cạnh các tệp trong basename.out (ví dụ: foo.jpeg -> foo.out). Chúng tôi muốn chạy my_script một lần cho mỗi lõi máy tính có và chúng tôi muốn chạy nó trên máy tính địa phương, quá. Đối với các máy tính từ xa, chúng tôi muốn tệp được xử lý được chuyển sang máy tính đã cho. Khi my_script kết thúc, chúng tôi muốn foo.out chuyển lại và sau đó chúng tôi muốn foo.jpeg và foo.out bị xóa khỏi máy tính từ xa:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallel đảm bảo đầu ra từ mỗi công việc không trộn lẫn, vì vậy bạn có thể sử dụng đầu ra làm đầu vào cho một chương trình khác:

some-command | parallel do-something | postprocess

Xem video để biết thêm ví dụ: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


35
2018-06-10 01:37



Lưu ý rằng điều này thực sự hữu ích khi sử dụng find lệnh để tạo danh sách tệp, vì nó không chỉ ngăn sự cố khi có khoảng trắng bên trong tên tệp xuất hiện trong for i in ...; do nhưng tìm cũng có thể làm find -name \*.extension1 -or -name \*.extension2 mà GNU song song {.} có thể xử lý rất độc đáo. - Leo Izen
Cộng 1 mặc dù cat là, tất nhiên, vô ích. - tripleee
@tripleee Re: Sử dụng mèo vô dụng. Xem oletange.blogspot.dk/2013/10/useless-use-of-cat.html - Ole Tange
Oh, đó là bạn! Ngẫu nhiên, bạn có thể cập nhật liên kết trên blog đó không? Vị trí partmaps.org là đáng tiếc đã chết, nhưng redirector Iki nên tiếp tục làm việc. - tripleee


maxjobs = 4
parallelize () {
        trong khi [$ # -gt 0]; làm
                jobcnt = (`jobs -p`)
                nếu [$ {# jobcnt [@]} -lt $ maxjobs]; sau đó
                        do-something $ 1 &
                        sự dịch chuyển
                khác
                        ngủ 1
                fi
        làm xong
        chờ đợi
}

song song arg1 arg2 "5 args cho công việc thứ ba" arg4 ...

22
2017-09-01 18:00



Nhận ra có một số nghiêm trọng underquoting đang diễn ra ở đây vì vậy bất kỳ công việc nào yêu cầu không gian trong các đối số sẽ thất bại nghiêm trọng; hơn nữa, kịch bản này sẽ ăn CPU của bạn còn sống trong khi nó chờ đợi cho một số công việc để kết thúc nếu nhiều công việc được yêu cầu hơn maxjobs cho phép. - lhunath
Cũng lưu ý rằng điều này giả định kịch bản của bạn không làm bất cứ điều gì khác để làm với công việc; nếu bạn đang có, nó sẽ đếm những người đối với maxjobs là tốt. - lhunath
Bạn có thể muốn sử dụng "jobs -pr" để giới hạn công việc đang chạy. - amphetamachine
Thêm lệnh ngủ để ngăn vòng lặp while lặp lại mà không bị ngắt, trong khi chờ đợi lệnh do-something đang chạy kết thúc. Nếu không, vòng lặp này về cơ bản sẽ lấy một trong các lõi CPU. Điều này cũng giải quyết mối quan tâm của @lhunath. - euphoria83


Thay vì một bash đơn giản, sử dụng Makefile, sau đó chỉ định số lượng công việc đồng thời với make -jX trong đó X là số lượng công việc để chạy cùng một lúc.

Hoặc bạn có thể sử dụng wait ("man wait"): khởi chạy một số tiến trình con, gọi wait - nó sẽ thoát khi con xử lý xong.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

Nếu bạn cần lưu trữ kết quả của công việc, sau đó gán kết quả của họ cho một biến. Sau wait bạn chỉ cần kiểm tra những gì biến chứa.


11
2017-09-01 16:50



Cảm ơn vì điều này, mặc dù mã không được hoàn thành, nó cho tôi câu trả lời cho một vấn đề mà tôi đang làm việc. - gerikson
vấn đề duy nhất là nếu bạn giết các kịch bản tiền cảnh (một với vòng lặp) các công việc đang chạy sẽ không bị giết cùng nhau - Girardi


Có thể thử một tiện ích song song thay vì viết lại vòng lặp? Tôi là một fan hâm mộ lớn của xjobs. Tôi thường xuyên sử dụng xjobs để sao chép các tệp hàng loạt trên mạng của mình, thường là khi thiết lập máy chủ cơ sở dữ liệu mới. http://www.maier-komor.de/xjobs.html


8
2017-09-01 16:55





Dưới đây là một giải pháp thay thế có thể được chèn vào .bashrc và được sử dụng cho một lớp lót hàng ngày:

function pwait() {
    while [ $(jobs -p | wc -l) -ge $1 ]; do
        sleep 1
    done
}

Để sử dụng nó, tất cả mọi thứ phải làm là đặt & sau khi các công việc và một cuộc gọi pwait, tham số cho số lượng các quá trình song song:

for i in *; do
    do_something $i &
    pwait 10
done

Nó sẽ đẹp hơn khi sử dụng wait thay vì bận rộn chờ đợi trên đầu ra của jobs -p, nhưng dường như không có giải pháp rõ ràng nào để chờ cho đến khi bất kỳ công việc nào được hoàn thành thay vì tất cả chúng.


8
2018-05-19 03:40





Trong khi làm điều này ngay trong bash có lẽ là không thể, bạn có thể làm một bán khá dễ dàng. bstark đưa ra một xấp xỉ đúng đắn nhưng có những sai sót sau:

  • Tách từ: Bạn không thể chuyển bất kỳ công việc nào cho nó sử dụng bất kỳ ký tự nào sau đây trong đối số của chúng: dấu cách, tab, dòng mới, dấu sao, dấu chấm hỏi. Nếu bạn làm thế, mọi thứ sẽ phá vỡ, có thể bất ngờ.
  • Nó dựa trên phần còn lại của kịch bản của bạn để không nền bất cứ điều gì. Nếu bạn làm, hoặc sau đó bạn thêm một cái gì đó vào kịch bản được gửi trong nền bởi vì bạn quên bạn không được phép sử dụng các công việc nền vì đoạn mã của mình, mọi thứ sẽ phá vỡ.

Một phép tính xấp xỉ khác không có những sai sót sau đây:

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

Lưu ý rằng cái này có thể dễ dàng thích nghi để kiểm tra mã thoát của mỗi công việc khi nó kết thúc để bạn có thể cảnh báo người dùng nếu công việc thất bại hoặc đặt mã thoát cho scheduleAll theo số lượng công việc thất bại, hoặc một cái gì đó.

Vấn đề với mã này chỉ là:

  • Nó lên kế hoạch bốn (trong trường hợp này) công việc tại một thời điểm và sau đó chờ đợi cho tất cả bốn để kết thúc. Một số có thể được thực hiện sớm hơn những người khác mà sẽ gây ra đợt tiếp theo của bốn công việc để chờ đợi cho đến khi dài nhất của lô trước đó được thực hiện.

Một giải pháp chăm sóc vấn đề cuối cùng này sẽ phải sử dụng kill -0 để thăm dò xem liệu bất kỳ quy trình nào đã biến mất thay vì wait và lên kế hoạch cho công việc tiếp theo. Tuy nhiên, điều đó giới thiệu một vấn đề nhỏ mới: bạn có một điều kiện chủng tộc giữa một công việc kết thúc, và kill -0 kiểm tra xem nó đã kết thúc chưa. Nếu công việc đã kết thúc và một quá trình khác trên hệ thống của bạn khởi động cùng một lúc, hãy lấy một PID ngẫu nhiên, điều này xảy ra với công việc vừa hoàn thành, kill -0 sẽ không nhận thấy công việc của bạn đã hoàn thành và mọi thứ sẽ phá vỡ một lần nữa.

Một giải pháp hoàn hảo là không thể bash.


6
2018-05-19 07:26





Nếu bạn quen thuộc với make lệnh, hầu hết thời gian bạn có thể thể hiện danh sách các lệnh bạn muốn chạy dưới dạng một tệp makefile. Ví dụ, nếu bạn cần chạy $ SOME_COMMAND trên các tệp * .input mỗi cái tạo ra * .output, bạn có thể sử dụng makefile

INPUT = a.input b.input
OUTPUT = $ (INPUT: .input = .output)

%.đầu ra đầu vào
    $ (SOME_COMMAND) $ <$ @

tất cả: $ (OUTPUT)

và sau đó chỉ cần chạy

make -j <NUMBER>

để chạy tối đa NUMBER lệnh song song.


5
2018-05-21 20:33