memutuskan antara subproses, multiprocessing, dan utas dengan Python?

110

Saya ingin memparalelkan program Python saya sehingga dapat menggunakan banyak prosesor pada mesin yang menjalankannya. Paralelisasi saya sangat sederhana, karena semua "utas" paralel dari program ini independen dan menulis outputnya ke file terpisah. Saya tidak memerlukan utas untuk bertukar informasi tetapi sangat penting bahwa saya tahu kapan utas selesai karena beberapa langkah pipa saya bergantung pada outputnya.

Portabilitas itu penting, karena saya ingin ini berjalan pada versi Python apa pun di Mac, Linux, dan Windows. Dengan batasan ini, modul Python mana yang paling tepat untuk mengimplementasikan ini? Saya mencoba untuk memutuskan antara utas, subproses, dan multiproses, yang semuanya tampaknya menyediakan fungsionalitas terkait.

Ada pemikiran tentang ini? Saya ingin solusi paling sederhana yang portabel.

Vaibhav Mule
sumber
Terkait: stackoverflow.com/questions/1743293/… (baca jawaban saya di sana untuk melihat mengapa utas adalah non-starter untuk kode Python murni)
1
"Semua versi Python" terlalu kabur. Python 2.3? 1.x? 3.x? Ini hanyalah kondisi yang tidak mungkin untuk dipenuhi.
detly

Jawaban:

64

multiprocessingadalah jenis modul pisau Swiss-army yang bagus. Ini lebih umum daripada utas, karena Anda bahkan dapat melakukan komputasi jarak jauh. Oleh karena itu, ini adalah modul yang saya sarankan Anda gunakan.

The subprocessModul juga akan memungkinkan Anda untuk memulai beberapa proses, tapi saya menemukan untuk menjadi kurang nyaman digunakan daripada modul multiprocessing baru.

Utas terkenal halus, dan, dengan CPython, Anda sering dibatasi pada satu inti, dengan mereka (meskipun, seperti disebutkan di salah satu komentar, Kunci Penerjemah Global (GIL) dapat dirilis dalam kode C yang dipanggil dari kode Python) .

Saya yakin bahwa sebagian besar fungsi dari tiga modul yang Anda kutip dapat digunakan dengan cara yang tidak bergantung platform. Di sisi portabilitas, perhatikan bahwa multiprocessinghanya tersedia dalam standar sejak Python 2.6 (versi untuk beberapa versi Python yang lebih lama memang ada). Tapi itu modul yang bagus!

Eric O Lebigot
sumber
1
untuk tugas, saya hanya menggunakan modul "multiprocessing" dan metode pool.map () nya. sepotong kue !
kmonsoor
Apakah hal seperti Celery juga sedang dipertimbangkan? Mengapa demikian atau tidak?
pengguna3245268
Sejauh yang saya tahu Celery lebih terlibat (Anda harus menginstal beberapa broker pesan), tetapi ini adalah opsi yang mungkin harus dipertimbangkan, tergantung pada masalah yang dihadapi.
Eric O Lebigot
186

Bagi saya ini sebenarnya cukup sederhana:

The subproses opsi:

subprocessadalah untuk menjalankan executable lain --- pada dasarnya ini adalah pembungkus os.fork()dan os.execve()dengan beberapa dukungan untuk pipa saluran opsional (menyiapkan PIPE ke dan dari subproses. Jelas Anda dapat mekanisme komunikasi antar proses (IPC) lainnya, seperti soket, atau Posix atau Memori bersama SysV. Tetapi Anda akan dibatasi pada antarmuka dan saluran IPC apa pun yang didukung oleh program yang Anda panggil.

Umumnya, seseorang menggunakan sembarang subprocess- cukup memanggil beberapa utilitas eksternal dan membaca kembali outputnya atau menunggu penyelesaiannya (mungkin membaca hasilnya dari file sementara, atau setelah diposting ke beberapa database).

Namun seseorang dapat menelurkan ratusan subproses dan melakukan polling. Classh utilitas favorit pribadi saya melakukan hal itu. Kerugian terbesar dari subprocessmodul ini adalah dukungan I / O umumnya memblokir. Ada draf PEP-3145 untuk memperbaikinya di beberapa versi Python 3.x yang akan datang dan asyncproc alternatif (Peringatan yang mengarahkan hak ke unduhan, bukan ke dokumentasi atau README). Saya juga menemukan bahwa relatif mudah untuk hanya mengimpor fcntldan memanipulasi Popendeskriptor file PIPE Anda secara langsung --- meskipun saya tidak tahu apakah ini portabel untuk platform non-UNIX.

(Pembaruan: 7 Agustus 2019: Dukungan Python 3 untuk subproses ayncio : Subproses asyncio )

subprocess hampir tidak memiliki dukungan penanganan peristiwa ... meskipun Anda dapat menggunakan signalmodul dan sinyal UNIX / Linux jadul biasa --- mematikan proses Anda dengan lembut, seolah-olah.

The multiprocessing opsi:

multiprocessingadalah untuk menjalankan fungsi dalam kode (Python) yang ada dengan dukungan untuk komunikasi yang lebih fleksibel di antara rangkaian proses ini. Secara khusus, yang terbaik adalah membangun multiprocessingIPC Anda di sekitar objek modul Queuejika memungkinkan, tetapi Anda juga dapat menggunakan Eventobjek dan berbagai fitur lainnya (beberapa di antaranya, mungkin, dibangun di sekitar mmapdukungan pada platform di mana dukungan itu cukup).

multiprocessingModul Python dimaksudkan untuk menyediakan antarmuka dan fitur yang sangat mirip dengan threading sementara memungkinkan CPython untuk menskalakan pemrosesan Anda di antara banyak CPU / core meskipun ada GIL (Global Interpreter Lock). Ini memanfaatkan semua penguncian SMP dan upaya koherensi yang dilakukan oleh pengembang kernel OS Anda.

The threading opsi:

threadingadalah untuk rentang aplikasi yang cukup sempit yang terikat I / O (tidak perlu diskalakan di beberapa inti CPU) dan yang diuntungkan dari latensi yang sangat rendah dan overhead switching dari peralihan thread (dengan memori inti bersama) vs. proses / pengalihan konteks. Di Linux ini hampir merupakan himpunan kosong (waktu peralihan proses Linux sangat dekat dengan sakelar utasnya).

threadingmenderita dua kelemahan utama dengan Python .

Satu, tentu saja, adalah implementasi spesifik --- kebanyakan mempengaruhi CPython. Itulah GIL. Untuk sebagian besar, sebagian besar program CPython tidak akan mendapatkan keuntungan dari ketersediaan lebih dari dua CPU (inti) dan seringkali kinerja akan menderita karena pertentangan penguncian GIL.

Masalah yang lebih besar yang tidak spesifik untuk implementasi, adalah bahwa utas berbagi memori yang sama, penangan sinyal, deskriptor file, dan sumber daya OS tertentu lainnya. Oleh karena itu, programmer harus sangat berhati-hati tentang penguncian objek, penanganan pengecualian, dan aspek lain dari kode mereka yang tidak kentara dan dapat mematikan, menghentikan, atau menghentikan keseluruhan proses (rangkaian utas).

Sebagai perbandingan, multiprocessingmodel memberikan setiap proses memorinya sendiri, deskriptor file, dll. Sebuah error atau pengecualian yang tidak tertangani di salah satu dari mereka hanya akan mematikan sumber daya itu dan menangani hilangnya proses anak atau saudara secara kuat bisa jauh lebih mudah daripada debugging, mengisolasi dan memperbaiki atau mengatasi masalah serupa di utas.

  • (Catatan: penggunaan threadingdengan sistem Python utama, seperti NumPy , mungkin menderita lebih sedikit dari pertentangan GIL daripada kebanyakan kode Python Anda sendiri. Itu karena mereka telah direkayasa secara khusus untuk melakukannya; bagian asli / biner dari NumPy, misalnya, akan merilis GIL saat itu aman).

The bengkok opsi:

Perlu juga dicatat bahwa Twisted menawarkan alternatif lain yang elegan dan sangat menantang untuk dipahami . Pada dasarnya, dengan risiko terlalu menyederhanakan ke titik di mana penggemar Twisted dapat menyerbu rumah saya dengan garpu rumput dan obor, Twisted menyediakan multi-tasking koperasi yang digerakkan oleh acara dalam proses (tunggal) apa pun.

Untuk memahami bagaimana hal ini dimungkinkan, seseorang harus membaca tentang fitur-fitur select()(yang dapat dibangun di sekitar select () atau poll () atau panggilan sistem OS serupa). Pada dasarnya itu semua didorong oleh kemampuan untuk membuat permintaan OS untuk tidur sambil menunggu aktivitas apa pun di daftar deskriptor file atau beberapa waktu tunggu.

Kebangkitan dari masing-masing panggilan ini ke select()adalah sebuah peristiwa --- baik yang melibatkan input yang tersedia (dapat dibaca) pada sejumlah soket atau deskriptor file, atau ruang buffer menjadi tersedia pada beberapa deskriptor atau soket lain (dapat ditulis), beberapa kondisi luar biasa (TCP out-of-band PUSH'd packets, misalnya), atau TIMEOUT.

Jadi, model pemrograman Twisted dibangun untuk menangani kejadian ini kemudian melakukan perulangan pada pengendali "utama" yang dihasilkan, memungkinkannya untuk mengirimkan kejadian ke penangan Anda.

Saya pribadi menganggap nama, Twisted sebagai menggugah model pemrograman ... karena pendekatan Anda terhadap masalah harus, dalam arti tertentu, "dipelintir" dari dalam ke luar. Daripada menganggap program Anda sebagai rangkaian operasi pada data masukan dan keluaran atau hasil, Anda menulis program Anda sebagai layanan atau daemon dan menentukan bagaimana reaksinya terhadap berbagai peristiwa. (Sebenarnya inti "putaran utama" dari program Twisted adalah (biasanya? Selalu?) A reactor()).

The tantangan besar untuk menggunakan twisted melibatkan memutar pikiran Anda sekitar acara didorong Model dan juga menghindari penggunaan setiap perpustakaan kelas atau toolkit yang tidak ditulis untuk bekerja sama dalam kerangka twisted. Inilah sebabnya mengapa Twisted menyediakan modulnya sendiri untuk penanganan protokol SSH, untuk kutukan, dan subproses / fungsi Popennya sendiri, dan banyak modul dan penangan protokol lainnya yang, pada awalnya, akan terlihat menduplikasi hal-hal di pustaka standar Python.

Saya pikir itu berguna untuk memahami Twisted pada tingkat konseptual bahkan jika Anda tidak pernah berniat menggunakannya. Ini dapat memberikan wawasan tentang kinerja, perselisihan, dan penanganan acara dalam penanganan threading, multiprosesing dan bahkan subproses Anda serta setiap pemrosesan terdistribusi yang Anda lakukan.

( Catatan: Versi lebih baru dari Python 3.x termasuk fitur asyncio (asynchronous I / O) seperti async def , dekorator @ async.coroutine , dan kata kunci await , dan hasil dari dukungan di masa mendatang . Semua ini secara kasar mirip dengan Memutar dari perspektif proses (multitasking koperasi)). (Untuk status dukungan Twisted saat ini untuk Python 3, lihat: https://twistedmatrix.com/documents/current/core/howto/python3.html )

The didistribusikan opsi:

Namun bidang pemrosesan lain yang belum Anda tanyakan, tetapi yang patut dipertimbangkan, adalah pemrosesan terdistribusi . Ada banyak alat dan kerangka kerja Python untuk pemrosesan terdistribusi dan komputasi paralel. Secara pribadi saya pikir yang paling mudah digunakan adalah yang paling tidak sering dianggap ada di ruang itu.

Membangun pemrosesan terdistribusi di sekitar Redis hampir tidak mudah . Seluruh penyimpanan kunci dapat digunakan untuk menyimpan unit dan hasil kerja, Redis LIST dapat digunakan sebagai Queue()objek sejenis, dan dukungan PUB / SUB dapat digunakan untuk Eventpenanganan serupa. Anda dapat melakukan hashing pada kunci dan nilai penggunaan Anda, yang direplikasi di seluruh cluster instans Redis yang longgar, untuk menyimpan topologi dan pemetaan token hash guna memberikan hashing dan fail-over yang konsisten untuk penskalaan di luar kapasitas instans tunggal mana pun untuk mengoordinasikan pekerja Anda dan data marshaling (acar, JSON, BSON, atau YAML) di antaranya.

Tentu saja saat Anda mulai membangun skala yang lebih besar dan solusi yang lebih canggih di sekitar Redis, Anda menerapkan ulang banyak fitur yang telah diselesaikan menggunakan, Celery , Apache Spark dan Hadoop , Zookeeper , dlld , Cassandra , dan seterusnya. Semuanya memiliki modul untuk akses Python ke layanan mereka.

[Pembaruan: Beberapa sumber daya untuk dipertimbangkan jika Anda mempertimbangkan Python untuk komputasi intensif di seluruh sistem terdistribusi: IPython Parallel dan PySpark . Meskipun ini adalah sistem komputasi terdistribusi untuk tujuan umum, sistem ini sangat mudah diakses dan ilmu data subsistem dan analitik yang populer].

Kesimpulan

Di sana Anda memiliki keseluruhan alternatif pemrosesan untuk Python, dari utas tunggal, dengan panggilan sinkron sederhana ke sub-proses, kumpulan subproses yang disurvei, utas dan multiprosesing, multi-tasking koperasi yang digerakkan oleh peristiwa, dan keluar ke pemrosesan terdistribusi.

Jim Dennis
sumber
1
Sulit untuk menggunakan multiprocessing dengan kelas / OOP sekalipun.
Tjorriemorrie
2
@Tjorriemorrie: Saya akan menebak bahwa yang Anda maksudkan adalah sulit untuk mengirimkan panggilan metode ke instance objek yang mungkin berada dalam proses lain. Saya menyarankan bahwa ini adalah masalah yang sama dengan yang Anda alami dengan utas, tetapi lebih mudah terlihat (daripada rapuh dan tunduk pada kondisi balapan yang tidak jelas). Saya akan berpikir bahwa pendekatan yang disarankan adalah mengatur agar semua pengiriman tersebut terjadi melalui objek Queue, yang berfungsi single threaded, multi-threaded dan lintas proses. (Dengan beberapa implementasi Redis atau Celery Queue, bahkan di seluruh cluster node)
Jim Dennis
2
Ini adalah jawaban yang sangat bagus. Saya berharap itu ada dalam pengantar konkurensi di dokumen Python3.
root-11
1
@ root-11 Anda dipersilakan untuk mengusulkannya ke pengelola dokumen; Saya telah menerbitkannya di sini untuk penggunaan gratis. Anda dan mereka dipersilakan untuk menggunakannya, secara keseluruhan atau sebagian.
Jim Dennis
"Bagi saya ini sebenarnya cukup sederhana:" Suka. terima kasih banyak
jerome
5

Dalam kasus serupa, saya memilih proses terpisah dan sedikit komunikasi yang diperlukan melalui soket jaringan. Ini sangat portabel dan cukup mudah dilakukan dengan python, tetapi mungkin tidak lebih sederhana (dalam kasus saya, saya juga memiliki kendala lain: komunikasi dengan proses lain yang ditulis dalam C ++).

Dalam kasus Anda, saya mungkin akan menggunakan multiproses, karena utas python, setidaknya saat menggunakan CPython, bukan utas asli. Ya, mereka adalah utas sistem asli tetapi modul C yang dipanggil dari Python mungkin atau mungkin tidak melepaskan GIL dan mengizinkan utas lain menjalankannya saat memanggil kode pemblokiran.

kriss
sumber
4

Untuk menggunakan banyak prosesor di CPython, satu - satunya pilihan Anda adalah multiprocessingmodul. CPython menyimpan kunci pada internalnya ( GIL ) yang mencegah utas pada cpu lain untuk bekerja secara paralel. The multiprocessingModul menciptakan proses baru (seperti subprocess) dan mengelola komunikasi di antara mereka.

Jochen Ritzel
sumber
5
Itu tidak sepenuhnya benar, AFAIK Anda dapat merilis GIL menggunakan C API, dan ada implementasi Python lainnya seperti IronPython atau Jython yang tidak mengalami keterbatasan seperti itu. Tapi saya tidak memberi suara negatif.
Bastien Léonard
1

Keluar dan biarkan unix keluar untuk melakukan pekerjaan Anda:

gunakan iterpipes untuk membungkus subproses dan kemudian:

Dari situs Ted Ziuba

INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM proses paralel

ATAU

Gnu Parallel juga akan melayani

Anda bergaul dengan GIL sementara Anda mengirim anak laki-laki ruang belakang untuk melakukan pekerjaan multicore Anda.

chiggsy
sumber
6
"Portabilitas itu penting, karena saya ingin ini berjalan di semua versi Python di Mac, Linux, dan Windows."
detly
Dengan solusi ini, dapatkah Anda berinteraksi berulang kali dengan pekerjaan? Anda dapat melakukan ini dalam multiprocessing, tetapi saya rasa tidak demikian di subprocess.
abalter