Saya belajar cara menggunakan threading
dan multiprocessing
modul dalam Python untuk menjalankan operasi tertentu secara paralel dan mempercepat kode saya.
Saya menemukan ini sulit (mungkin karena saya tidak memiliki latar belakang teoritis tentang hal itu) untuk memahami apa perbedaan antara threading.Thread()
objek danmultiprocessing.Process()
.
Juga, tidak sepenuhnya jelas bagi saya bagaimana membuat instance antrian pekerjaan dan hanya memiliki 4 (misalnya) dari mereka yang berjalan secara paralel, sementara yang lain menunggu sumber daya untuk membebaskan sebelum dieksekusi.
Saya menemukan contoh dalam dokumentasi yang jelas, tetapi tidak terlalu lengkap; segera setelah saya mencoba sedikit memperumit masalah, saya menerima banyak kesalahan aneh (seperti metode yang tidak bisa diacungi, dan sebagainya).
Jadi, kapan saya harus menggunakan threading
danmultiprocessing
modul?
Bisakah Anda menghubungkan saya dengan beberapa sumber yang menjelaskan konsep di balik dua modul ini dan bagaimana menggunakannya dengan benar untuk tugas-tugas kompleks?
Thread
modul (disebut_thread
dengan python 3.x). Sejujurnya, aku sendiri tidak pernah mengerti perbedaannya ...Thread
/_thread
dokumentasi secara eksplisit, "primitif tingkat rendah". Anda dapat menggunakannya untuk membuat objek sinkronisasi khusus, untuk mengontrol urutan gabungan dari utas, dll. Jika Anda tidak dapat membayangkan mengapa Anda harus menggunakannya, jangan menggunakannya, dan tetap menggunakannyathreading
.Jawaban:
Apa yang dikatakan Giulio Franco adalah benar untuk multithreading vs. multiprocessing secara umum .
Namun, Python * memiliki masalah tambahan: Ada Global Interpreter Lock yang mencegah dua utas dalam proses yang sama dari menjalankan kode Python pada saat yang sama. Ini berarti bahwa jika Anda memiliki 8 core, dan mengubah kode Anda untuk menggunakan 8 thread, itu tidak akan dapat menggunakan CPU 800% dan menjalankan 8x lebih cepat; itu akan menggunakan CPU 100% yang sama dan berjalan pada kecepatan yang sama. (Pada kenyataannya, ini akan berjalan sedikit lebih lambat, karena ada overhead tambahan dari threading, bahkan jika Anda tidak memiliki data bersama, tetapi abaikan itu untuk saat ini.)
Ada pengecualian untuk ini. Jika perhitungan berat kode Anda tidak benar-benar terjadi di Python, tetapi di beberapa pustaka dengan kode C kustom yang melakukan penanganan GIL yang tepat, seperti aplikasi numpy, Anda akan mendapatkan manfaat kinerja yang diharapkan dari threading. Hal yang sama berlaku jika perhitungan berat dilakukan oleh beberapa subproses yang Anda jalankan dan tunggu.
Lebih penting lagi, ada kasus di mana ini tidak masalah. Misalnya, server jaringan menghabiskan sebagian besar waktunya membaca paket dari jaringan, dan aplikasi GUI menghabiskan sebagian besar waktunya menunggu acara pengguna. Salah satu alasan untuk menggunakan utas di server jaringan atau aplikasi GUI adalah untuk memungkinkan Anda melakukan "tugas latar belakang" yang sudah berjalan lama tanpa menghentikan utas dari melanjutkan ke paket layanan jaringan atau acara GUI. Dan itu berfungsi dengan baik dengan utas Python. (Dalam istilah teknis, ini berarti utas Python memberi Anda konkurensi, meskipun mereka tidak memberi Anda paralelisme inti.)
Tetapi jika Anda menulis program yang terikat CPU dengan Python murni, menggunakan lebih banyak utas umumnya tidak membantu.
Menggunakan proses yang terpisah tidak memiliki masalah dengan GIL, karena setiap proses memiliki GIL yang terpisah. Tentu saja Anda masih memiliki semua pengorbanan yang sama antara utas dan proses seperti dalam bahasa lain — lebih sulit dan lebih mahal untuk berbagi data antar proses daripada antar utas, mungkin mahal untuk menjalankan sejumlah besar proses atau untuk membuat dan menghancurkan mereka sering, dll. Tapi GIL sangat membebani keseimbangan terhadap proses, dengan cara yang tidak benar untuk, katakanlah, C atau Java. Jadi, Anda akan lebih sering menggunakan multiprosesor dalam Python daripada di C atau Java.
Sementara itu, filosofi "baterai termasuk" Python membawa kabar baik: Sangat mudah untuk menulis kode yang dapat diubah-ubah antara utas dan proses dengan perubahan satu-liner.
Jika Anda mendesain kode Anda dalam hal "pekerjaan" mandiri yang tidak membagikan apa pun dengan pekerjaan lain (atau program utama) kecuali input dan output, Anda dapat menggunakan
concurrent.futures
perpustakaan untuk menulis kode Anda di sekitar kumpulan utas seperti ini:Anda bahkan bisa mendapatkan hasil dari pekerjaan itu dan meneruskannya ke pekerjaan selanjutnya, menunggu hal-hal dalam urutan eksekusi atau dalam urutan penyelesaian, dll .; baca bagian
Future
objek untuk detailnya.Sekarang, jika ternyata program Anda terus-menerus menggunakan CPU 100%, dan menambahkan lebih banyak utas hanya membuatnya lebih lambat, maka Anda mengalami masalah GIL, jadi Anda perlu beralih ke proses. Yang harus Anda lakukan adalah mengubah baris pertama itu:
Satu-satunya peringatan nyata adalah bahwa argumen pekerjaan Anda dan nilai-nilai pengembalian harus acar (dan tidak mengambil terlalu banyak waktu atau ingatan untuk acar) untuk dapat digunakan lintas proses. Biasanya ini bukan masalah, tapi terkadang memang begitu.
Tetapi bagaimana jika pekerjaan Anda tidak bisa mandiri? Jika Anda dapat merancang kode Anda dalam hal pekerjaan yang menyampaikan pesan dari satu ke yang lain, itu masih cukup mudah. Anda mungkin harus menggunakan
threading.Thread
ataumultiprocessing.Process
bukannya mengandalkan kolam. Dan Anda harus membuatqueue.Queue
ataumultiprocessing.Queue
objek secara eksplisit. (Ada banyak opsi lain — pipa, soket, file dengan kawanan, ... tetapi intinya adalah, Anda harus melakukan sesuatu secara manual jika sihir otomatis dari seorang Executor tidak cukup.)Tetapi bagaimana jika Anda bahkan tidak bisa mengandalkan pesan yang lewat? Bagaimana jika Anda membutuhkan dua pekerjaan untuk keduanya mengubah struktur yang sama, dan melihat perubahan satu sama lain? Dalam hal ini, Anda perlu melakukan sinkronisasi manual (kunci, semaphore, kondisi, dll.) Dan, jika Anda ingin menggunakan proses, objek shared-memory eksplisit untuk boot. Ini terjadi ketika multithreading (atau multiprocessing) menjadi sulit. Jika Anda bisa menghindarinya, bagus; jika Anda tidak bisa, Anda harus membaca lebih banyak daripada yang dapat dimasukkan seseorang ke dalam jawaban SO.
Dari komentar, Anda ingin tahu apa yang berbeda antara utas dan proses dalam Python. Sungguh, jika Anda membaca jawaban Giulio Franco dan milik saya serta semua tautan kami, itu akan mencakup semuanya ... tetapi ringkasan pasti akan berguna, jadi begini:
ctypes
jenis.threading
modul tidak memiliki beberapa fitur darimultiprocessing
modul. (Anda dapat menggunakanmultiprocessing.dummy
untuk mendapatkan sebagian besar API yang hilang di atas utas, atau Anda dapat menggunakan modul tingkat yang lebih tinggi sukaconcurrent.futures
dan tidak khawatir tentang hal itu.)* Sebenarnya bukan Python, bahasa, yang memiliki masalah ini, tetapi CPython, implementasi "standar" dari bahasa itu. Beberapa implementasi lain tidak memiliki GIL, seperti Jython.
** Jika Anda menggunakan metode fork start untuk multi-pemrosesan — yang dapat Anda lakukan di sebagian besar platform non-Windows — setiap proses anak mendapatkan sumber daya apa pun yang dimiliki orang tua ketika anak dimulai, yang bisa menjadi cara lain untuk meneruskan data kepada anak-anak.
sumber
pickle
dokumen menjelaskannya), dan kemudian yang terburuk adalah pembungkus bodoh Andadef wrapper(obj, *args): return obj.wrapper(*args)
.Beberapa utas dapat ada dalam satu proses tunggal. Utas yang termasuk dalam proses yang sama berbagi area memori yang sama (dapat membaca dan menulis ke variabel yang sama, dan dapat saling mengganggu). Sebaliknya, berbagai proses hidup di area memori yang berbeda, dan masing-masing memiliki variabel sendiri. Untuk berkomunikasi, proses harus menggunakan saluran lain (file, pipa atau soket).
Jika Anda ingin memparalelkan perhitungan, Anda mungkin perlu multithreading, karena Anda mungkin ingin utas untuk bekerja sama pada memori yang sama.
Berbicara tentang kinerja, utas lebih cepat untuk dibuat dan dikelola daripada proses (karena OS tidak perlu mengalokasikan area memori virtual yang sama sekali baru), dan komunikasi antar-utas biasanya lebih cepat daripada komunikasi antar-proses. Tetapi utas lebih sulit diprogram. Utas dapat saling mengganggu, dan dapat menulis ke memori satu sama lain, tetapi cara ini terjadi tidak selalu jelas (karena beberapa faktor, terutama instruksi pemesanan ulang dan caching memori), sehingga Anda akan memerlukan sinkronisasi primitif untuk mengontrol akses ke variabel Anda.
sumber
threading
danmultiprocessing
.Saya yakin tautan ini menjawab pertanyaan Anda dengan cara yang elegan.
Singkatnya, jika salah satu sub-masalah Anda harus menunggu sementara yang lain selesai, multithreading baik (dalam operasi berat I / O, misalnya); sebaliknya, jika sub-masalah Anda benar-benar dapat terjadi pada saat yang bersamaan, disarankan untuk melakukan multi-pemrosesan. Namun, Anda tidak akan membuat lebih banyak proses daripada jumlah inti Anda.
sumber
Kutipan dokumentasi Python
Saya telah menyoroti kutipan dokumentasi Python kunci tentang Process vs Threads dan GIL di: Apa kunci juru bahasa global (GIL) di CPython?
Percobaan proses vs utas
Saya melakukan sedikit pembandingan untuk menunjukkan perbedaan lebih konkret.
Dalam benchmark, saya menghitung waktu kerja CPU dan IO untuk berbagai jumlah utas pada CPU 8 hyperthread . Pekerjaan yang disediakan per utas selalu sama, sehingga lebih banyak utas berarti lebih banyak total pekerjaan yang disediakan.
Hasilnya adalah:
Plot data .
Kesimpulan:
untuk pekerjaan yang terikat CPU, multi-pemrosesan selalu lebih cepat, mungkin karena GIL
untuk pekerjaan terikat IO. keduanya persis kecepatan yang sama
utas hanya meningkatkan hingga sekitar 4x daripada yang diharapkan 8x karena saya menggunakan mesin 8 hyperthread.
Bandingkan dengan C POSIX yang bekerja dengan CPU yang mencapai kecepatan 8x yang diharapkan: Apa arti 'nyata', 'pengguna' dan 'sistem' dalam output waktu (1)?
TODO: Saya tidak tahu alasannya, pasti ada inefisiensi Python lain yang ikut bermain.
Kode uji:
GitHub kode upstream + plotting pada direktori yang sama .
Diuji pada Ubuntu 18.10, Python 3.6.7, dalam laptop Lenovo ThinkPad P51 dengan CPU: Intel Core i7-7820HQ CPU (4 core / 8 threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512HAJQ- 000L7 (3.000 MB / s).
Visualisasikan utas mana yang berjalan pada waktu tertentu
Posting ini https://rohanvarma.me/GIL/ mengajari saya bahwa Anda dapat menjalankan panggilan balik setiap kali utas dijadwalkan dengan
target=
argumenthreading.Thread
dan sama untukmultiprocessing.Process
.Ini memungkinkan kami untuk melihat thread mana yang berjalan pada setiap waktu. Ketika ini selesai, kita akan melihat sesuatu seperti (saya membuat grafik khusus ini):
yang akan menunjukkan bahwa:
sumber
Berikut adalah beberapa data kinerja untuk python 2.6.x yang meminta untuk mempertanyakan gagasan bahwa threading lebih berkinerja lebih daripada multiprocessing dalam skenario IO-terikat. Hasil ini berasal dari Sistem IBM 40-prosesor x3650 M4 BD.
Pemrosesan IO-Bound: Pool Proses berkinerja lebih baik daripada Thread Pool
CPU-Bound Processing: Process Pool tampil lebih baik daripada Thread Pool
Ini bukan tes yang ketat, tetapi mereka mengatakan kepada saya bahwa multi-pemrosesan tidak sepenuhnya tidak sebanding dibandingkan dengan threading.
Kode yang digunakan dalam konsol python interaktif untuk pengujian di atas
sumber
>>> do_work(50, 300, 'thread', 'fileio') --> 237.557 ms
>>> do_work(50, 300, 'process', 'fileio') --> 323.963 ms
>>> do_work(50, 2000, 'thread', 'square') --> 232.082 ms
>>> do_work(50, 2000, 'process', 'square') --> 282.785 ms
Ya, sebagian besar pertanyaan dijawab oleh Giulio Franco. Saya akan menjelaskan lebih lanjut tentang masalah konsumen-produsen, yang saya kira akan menempatkan Anda di jalur yang benar untuk solusi Anda menggunakan aplikasi multithreaded.
Anda dapat membaca lebih lanjut tentang sinkronisasi primitif dari:
Kode semu di atas. Saya kira Anda harus mencari masalah produsen-konsumen untuk mendapatkan lebih banyak referensi.
sumber