Empat tugas secara paralel ... bagaimana saya melakukannya?

23

Saya memiliki banyak gambar PNG di direktori. Saya memiliki aplikasi bernama pngout yang saya jalankan untuk mengompres gambar-gambar ini. Aplikasi ini disebut dengan script yang saya lakukan. Masalahnya adalah skrip ini melakukan satu per satu, seperti ini:

FILES=(./*.png)
for f in  "${FILES[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 $f R${f/\.\//}
done

Memproses hanya satu file pada satu waktu, membutuhkan banyak waktu. Setelah menjalankan aplikasi ini, saya melihat bahwa CPU hanya 10%. Jadi saya menemukan bahwa saya dapat membagi file-file ini dalam 4 batch, menempatkan setiap batch dalam direktori dan memecat 4, dari empat jendela terminal, empat proses, jadi saya memiliki empat contoh skrip saya, pada saat yang sama, memproses gambar-gambar dan pekerjaan membutuhkan 1/4 waktu.

Masalah kedua adalah saya kehilangan waktu untuk membagi gambar dan kumpulan dan menyalin naskah ke empat direktori, buka 4 terminal windows, bla bla ...

Bagaimana melakukannya dengan satu skrip, tanpa harus membagi apa pun?

Maksud saya dua hal: pertama bagaimana saya dari skrip bash, menjalankan proses ke latar belakang? (cukup tambahkan & ke akhir?) Kedua: bagaimana saya berhenti mengirim tugas ke latar belakang setelah mengirim tugas keempat dan menempatkan skrip untuk menunggu sampai tugas berakhir? Maksud saya, hanya mengirim tugas baru ke latar belakang sebagai salah satu tugas berakhir, menjaga selalu 4 tugas secara paralel? jika saya tidak melakukan itu, loop akan memuntahkan banyak tugas ke latar belakang dan CPU akan menyumbat.

SpaceDog
sumber
Lihat juga Paralelisasi untuk loop
Gilles 'SO- stop being evil'

Jawaban:

33

Jika Anda memiliki salinan xargsyang mendukung eksekusi paralel -P, Anda dapat melakukannya

printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}

Untuk ide lain, wiki Wooledge Bash memiliki bagian dalam artikel Manajemen Proses yang menjelaskan dengan tepat apa yang Anda inginkan.

jw013
sumber
2
Ada juga "gnu parallel" dan "xjobs" yang dirancang untuk kasus ini. Sebagian besar masalah selera yang Anda sukai.
Berkumandang
Bisakah Anda jelaskan perintah yang diusulkan? Terima kasih!
Eugene S
1
@ EugeneS Bisakah Anda sedikit lebih spesifik tentang bagian apa? Printf mengumpulkan semua file png dan meneruskannya melalui pipa ke xargs, yang mengumpulkan argumen dari input standar dan menggabungkannya menjadi argumen untuk pngoutperintah yang ingin dijalankan OP. Opsi kuncinya adalah -P 4, yang memberi tahu xargs untuk menggunakan hingga 4 perintah bersamaan.
jw013
2
Maaf karena tidak tepat. Saya secara khusus tertarik mengapa Anda menggunakan printffungsi di sini bukan hanya biasa ls .. | grep .. *.png? Saya juga tertarik pada xargsparameter yang Anda gunakan ( -0dan -I{}). Terima kasih!
Eugene S
3
@EugeneS Ini untuk kebenaran dan ketahanan maksimum. Nama file bukan baris, dan lstidak dapat digunakan untuk mengurai nama file dengan mudah dan aman . Satu-satunya karakter aman yang digunakan untuk membatasi nama file adalah \0dan /, karena setiap karakter lain, termasuk \n, dapat menjadi bagian dari nama file itu sendiri. The printfpenggunaan \0untuk nama file membatasi, dan -0menginformasikan xargsini. The -I{}memberitahu xargsuntuk mengganti {}dengan argumen.
jw013
8

Selain solusi yang sudah diajukan, Anda dapat membuat makefile yang menjelaskan cara membuat file terkompresi dari tidak terkompresi, dan gunakan make -j 4untuk menjalankan 4 pekerjaan secara paralel. Masalahnya adalah Anda perlu memberi nama file terkompresi dan tidak dikompresi secara berbeda, atau menyimpannya di direktori yang berbeda, jika tidak menulis aturan make yang masuk akal tidak akan mungkin.

9000
sumber
5

Untuk menjawab dua pertanyaan Anda:

  • ya, menambahkan & di akhir baris akan memerintahkan Anda untuk memulai proses latar belakang.
  • menggunakan waitperintah, Anda dapat meminta shell untuk menunggu semua proses di latar belakang selesai sebelum melanjutkan lebih jauh.

Berikut skrip yang dimodifikasi sehingga jdigunakan untuk melacak jumlah proses latar belakang. Ketika NB_CONCURRENT_PROCESSEStercapai, skrip akan diatur ulang jke 0 dan menunggu semua proses latar belakang selesai sebelum melanjutkan kembali pelaksanaannya.

files=(./*.png)
nb_concurrent_processes=4
j=0
for f in "${files[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 "$f" R"${f/\.\//}" &
        ((++j == nb_concurrent_processes)) && { j=0; wait; }
done
Frederik Deweerdt
sumber
1
Ini akan menunggu yang terakhir dari empat proses bersamaan dan kemudian akan memulai serangkaian empat lainnya. Mungkin orang harus membangun array dari empat PID dan kemudian menunggu PID tertentu ini?
Nils
Hanya untuk menjelaskan perbaikan saya ke kode: (1) Sebagai masalah gaya, hindari semua nama variabel huruf besar karena berpotensi bertentangan dengan variabel shell internal. (2) Menambahkan kutipan untuk $fdll. (3) Gunakan [untuk skrip yang kompatibel dengan POSIX, tetapi untuk bash murni [[selalu lebih disukai. Dalam hal ini, ((lebih tepat untuk aritmatika.
jw013