Soal 1Karena GIL Python (Global Interpreter Lock), menambah lebih banyak thread tidak memparalelkan pekerjaan jenis apa?
threading dan multiprocessing — Thread, Process, dan GIL
Beda thread dan process, alasan CPU-bound tak paralel karena GIL, ThreadPoolExecutor vs multiprocessing.Pool, panggilan subprocess perintah eksternal dan alur pakainya divisualkan.
Toolkit concurrency dan parallelism Python — threading (thread), multiprocessing (process), dan subprocess (perintah eksternal) — disandingkan berdampingan. Sandbox Python berbasis browser tidak bisa membuat thread atau process sungguhan, sehingga artikel ini memakai diagram dan blok kode read-only untuk memantapkan konsepnya.
Kode di sini tidak benar-benar berjalan
Blok code di artikel ini adalah contoh yang ditujukan untuk environment Python sungguhan. Pemanggilan seperti threading.Thread.start() atau multiprocessing.Pool.map() tidak akan bekerja di sandbox browser karena runtime tidak bisa membuat OS thread atau OS process. Sebagai gantinya, ada quiz pemeriksaan konsep di akhir.
Process vs thread
Process adalah unit eksekusi yang diberikan OS — ia memiliki memory space sendiri, dan process tidak saling mengganggu. Thread adalah unit eksekusi ringan di dalam satu process, dan ia berbagi memori dengan thread sesamanya. Berbagi itu membuat operan data jadi cepat, tetapi kamu harus mewaspadai race condition (beberapa thread menulis ke variabel yang sama secara bersamaan dan merusak hasilnya).
Model mental sederhananya: "process = berat tapi independen, thread = ringan tapi berbagi".
- Unit eksekusi independen dari sudut pandang OS
- Memori independen · Tanpa batasan GIL
- Biaya startup tinggi; parallelism sungguhan bekerja
- Unit yang benar-benar menjalankan kode di dalam process
- Memori bersama · Tunduk pada GIL
- Membuahkan hasil untuk pekerjaan I/O-bound
- Berjalan dengan beralih di dalam satu thread
- Lebih ringan lagi; bagus untuk banyak I/O konkuren
threading dan GIL — batasan thread Python
Python (CPython) memiliki mekanisme bernama GIL (Global Interpreter Lock) yang memberlakukan batasan: "hanya satu thread yang bisa mengeksekusi bytecode Python pada suatu waktu". Untuk pekerjaan berat CPU, menjalankannya di banyak thread secara konkuren secara efektif tidak lebih cepat dari satu thread.
Sebaliknya, selama pekerjaan I/O-bound (waktunya didominasi penantian respons eksternal — jaringan, file, DB) Python melepaskan GIL, sehingga threading memang mempercepat pekerjaan I/O-bound. Meskipun begitu, jika beban kerjanya I/O-bound, asyncio biasanya punya overhead lebih kecil dan lebih mudah ditulis, jadi utamakan asyncio untuk kode baru.
# threading: API tingkat rendah
import threading
def worker(name):
print(f"{name} started")
# lakukan sesuatu
print(f"{name} done")
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))
t1.start()
t2.start()
t1.join() # tunggu sampai selesai
t2.join()
# concurrent.futures: API tingkat tinggi (direkomendasikan)
from concurrent.futures import ThreadPoolExecutor
def fetch(url):
# di kode nyata: requests.get(url) atau pekerjaan I/O-bound lainnya
return f"fetched: {url}"
with ThreadPoolExecutor(max_workers=4) as executor:
urls = ["a.com", "b.com", "c.com"]
results = list(executor.map(fetch, urls))
print(results)
Pakai ThreadPoolExecutor untuk kode baru
Memakai threading.Thread secara langsung membuat manajemen lifecycle berantakan. concurrent.futures.ThreadPoolExecutor aman dikelola dengan with dan memungkinkan kamu memproses seluruh list dengan executor.map(func, iterable) sebagai satu API. Batas thread (max_workers) juga praktis untuk mencegah membanjiri server dengan koneksi.
Kapan threading cocok
threading / ThreadPoolExecutor bersinar saat kamu ingin mengisi waktu tunggu dengan task lain:
- Memproses beberapa Web API / query DB / file I/O secara konkuren
- Memparalelkan library sinkron yang sudah ada (tanpa dukungan async)
- Membaca output dari beberapa process yang dimunculkan subprocess secara konkuren
- Menjalankan pekerjaan background tanpa memblokir main loop GUI
multiprocessing — parallelism sungguhan
multiprocessing adalah modul untuk memunculkan beberapa process Python dan menjalankannya secara paralel. Karena process bebas dari batasan GIL, kamu bisa menjalankan pekerjaan CPU-bound dalam parallelism sungguhan — pada CPU 4 core, image processing atau number crunching menjadi sekitar 4x lebih cepat.
from multiprocessing import Pool
def heavy(n):
return sum(i * i for i in range(n)) # pekerjaan CPU-bound
if __name__ == "__main__": # bentuk wajib untuk multiprocessing
with Pool(processes=4) as pool:
results = pool.map(heavy, [10**6, 10**6, 10**6, 10**6])
print("sum:", sum(results))
multiprocessing membutuhkan `if __name__ == '__main__':`
multiprocessing bekerja dengan menjalankan ulang skrip parent di setiap process anak, jadi memanggil Pool(...).map(...) di top level memicu rekursi tak terhingga dan meledak. Metode spawn di Windows / macOS sangat ketat soal ini — selalu letakkan kode utamamu di dalam blok if __name__ == "__main__":.
subprocess — perintah eksternal
subprocess adalah modul untuk memanggil perintah eksternal (perintah shell OS) dari Python — menjalankan git status, mengonversi video dengan ffmpeg, memanggil shell script, dan kasus pemakaian "menjalankan program yang bukan Python" lainnya. Namanya mirip multiprocessing, tetapi alat yang sama sekali berbeda.
import subprocess
result = subprocess.run(
["git", "status", "--short"],
capture_output=True,
text=True,
check=True, # melempar CalledProcessError saat gagal
)
print(result.stdout)
print("return code:", result.returncode)
Alur keputusan — yang mana kamu pilih?
Memilih di antara asyncio / threading / multiprocessing / subprocess tergantung dua sumbu: "CPU-bound atau I/O-bound?" dan "Di dalam Python atau perintah eksternal?". Diagram alur di bawah menghilangkan sebagian besar tebak-tebakannya.
| Beban kerja | Pilih | Alasannya |
|---|---|---|
| Memanggil 100 Web API secara konkuren | asyncio | I/O-bound; ringan dan mudah ditulis |
| Memparalelkan HTTP client sinkron yang sudah ada | threading (ThreadPoolExecutor) | Jika library tidak punya dukungan async, pakai threading |
| Memparalelkan image processing di 4 core | multiprocessing | Pekerjaan CPU-bound butuh process untuk menghindari GIL |
Menjalankan perintah seperti git atau ffmpeg | subprocess | Khusus untuk memanggil program di luar Python |
| Jutaan operasi matematika sederhana | NumPy / Cython | Vektorisasi mengalahkan parallelism level Python |
Benar-benar CPU-bound lebih jarang dari yang kamu kira
Banyak kode Python yang terlihat seperti macet di komputasi sebenarnya menjadi 100x lebih cepat dengan vektorisasi memakai NumPy / Pandas / Cython. Sebelum mengejar 4x dengan multiprocessing, periksa dulu apa yang harus kamu lakukan: NumPy untuk numerik, Pandas untuk data, regex yang dioptimalkan untuk string.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Yang mana kamu pakai untuk memanggil perintah eksternal seperti git status dari Python?
Soal 3Yang paling cocok untuk benar-benar memparalelkan komputasi numerik berat di 4 core CPU?