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:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
executor.submit(job, argument)
executor.map(some_function, collection_of_independent_things)
# ...
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:
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
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
atau multiprocessing.Process
bukannya mengandalkan kolam. Dan Anda harus membuat queue.Queue
atau multiprocessing.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:
- Utas berbagi data secara default; proses tidak.
- Sebagai konsekuensi dari (1), mengirim data antar proses umumnya membutuhkan pengawetan dan pembongkaran. **
- Sebagai konsekuensi lain dari (1), berbagi data secara langsung antara proses biasanya mengharuskan memasukkannya ke dalam format tingkat rendah seperti Nilai, Array, dan
ctypes
jenis.
- Proses tidak tunduk pada GIL.
- Pada beberapa platform (terutama Windows), proses jauh lebih mahal untuk dibuat dan dihancurkan.
- Ada beberapa batasan ekstra pada proses, beberapa di antaranya berbeda pada platform yang berbeda. Lihat panduan Pemrograman untuk detailnya.
- The
threading
modul tidak memiliki beberapa fitur dari multiprocessing
modul. (Anda dapat menggunakan multiprocessing.dummy
untuk mendapatkan sebagian besar API yang hilang di atas utas, atau Anda dapat menggunakan modul tingkat yang lebih tinggi suka concurrent.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.
Thread
modul (disebut_thread
dengan python 3.x). Sejujurnya, aku sendiri tidak pernah mengerti perbedaannya ...