Banyak program nyata justru menghabiskan sebagian besar waktunya untuk menunggu: menunggu respons API pihak ketiga, menunggu query database dieksekusi, atau menunggu file besar selesai dibaca dari disk. Selama penantian tersebut, prosesor menganggur padahal antrean pekerjaan lain masih panjang. Pada Chapter ini kita akan memetakan dua konsep yang sering tertukar — concurrency dan parallelism — lalu mengenali jenis beban kerja program kita agar dapat memilih alat yang tepat dari Python.
Concurrency Bukan Berarti Parallelism #
Concurrency adalah cara menstrukturkan program agar banyak pekerjaan dapat berjalan tumpang tindih: pekerjaan B boleh maju selama pekerjaan A sedang menunggu. Parallelism adalah eksekusi banyak pekerjaan pada saat yang persis bersamaan di beberapa core prosesor.
Analoginya begini: satu koki yang menyiapkan tiga pesanan dengan bergantian — memanaskan wajan untuk pesanan A, lalu memotong bahan pesanan B sambil menunggu wajan panas — sudah bekerja secara concurrent meskipun sendirian. Parallelism baru terjadi apabila dapur mempekerjakan tiga koki yang masing-masing menggarap satu pesanan sekaligus.
Mari kita lihat concurrency dalam bentuk paling nyata: dua thread yang mencetak langkah pekerjaannya secara bergantian. Kode ini masih memakai module threading yang akan kita bedah tuntas pada Chapter Threading:
import threading
import time
def tugas_a():
for langkah in range(1, 4):
time.sleep(0.25)
print(f"Tugas A - langkah {langkah}")
def tugas_b():
time.sleep(0.125)
for langkah in range(1, 4):
time.sleep(0.25)
print(f"Tugas B - langkah {langkah}")
ta = threading.Thread(target=tugas_a)
tb = threading.Thread(target=tugas_b)
ta.start()
tb.start()
ta.join()
tb.join()
print("Semua tugas selesai")Output yang Dihasilkan:
Tugas A - langkah 1
Tugas B - langkah 1
Tugas A - langkah 2
Tugas B - langkah 2
Tugas A - langkah 3
Tugas B - langkah 3
Semua tugas selesaiMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 13 (
ta = threading.Thread(target=tugas_a)): kita membuat objectThreadyang menunjuk ke functiontugas_a. Pada tahap ini belum ada kode yang dieksekusi. - Baris 15 (
ta.start()): methodstart()memerintahkan interpreter Python meminta thread baru kepada sistem operasi, lalu thread tersebut menjalankan function target. - Baris 17 (
ta.join()): methodjoin()membuat baris setelahnya menunggu sampai thread terkait selesai, sehingga barisprint("Semua tugas selesai")baru berjalan setelah kedua tugas benar-benar tuntas. - Baris 10 (
time.sleep(0.125)): jeda kecil ini membuat kedua thread bergantian mengisi layar. Urutan antar-baris dapat berbeda pada setiap eksekusi karena penjadwalan thread ditangani sistem operasi.
Beban Kerja I/O-Bound dan CPU-Bound #
Sebelum memilih alat, kita perlu tahu program kita sibuk menunggu atau sibuk menghitung. Dua kategori ini menentukan segalanya:
| Jenis beban | Contoh nyata | Ciri utama | Alat yang cocok |
|---|---|---|---|
| I/O-bound | HTTP request, query database, baca/tulis file, time.sleep() | CPU sering menganggur karena menunggu pihak luar | threading, asyncio |
| CPU-bound | kompresi gambar, parsing dokumen besar, perhitungan matematis | CPU bekerja penuh tanpa menunggu apa pun | multiprocessing |
Untuk pekerjaan I/O-bound, concurrency memberi manfaat nyata. Saat satu thread menunggu — time.sleep() di bawah mensimulasikan penantian jaringan — thread lain boleh maju:
import threading
import time
def unduh_berkas(nama):
time.sleep(0.5) # simulasi menunggu jaringan
return f"{nama} selesai diunduh"
mulai = time.perf_counter()
unduh_berkas("laporan.pdf")
unduh_berkas("foto.jpg")
berurutan = time.perf_counter() - mulai
mulai = time.perf_counter()
t1 = threading.Thread(target=unduh_berkas, args=("laporan.pdf",))
t2 = threading.Thread(target=unduh_berkas, args=("foto.jpg",))
t1.start()
t2.start()
t1.join()
t2.join()
paralel = time.perf_counter() - mulai
print(f"Berurutan : {berurutan:.2f} detik")
print(f"Bersamaan : {paralel:.2f} detik")Output yang Dihasilkan:
Berurutan : 1.00 detik
Bersamaan : 0.50 detikVersi berurutan menunggu dua kali secara berturut-turut (0,5 + 0,5 detik). Versi dua thread menunggu bersama-sama sehingga totalnya hanya sekitar 0,50 detik. Angka pastinya bergantung pada mesin, tetapi polanya konsisten: penantian yang tumpang tindih memangkas total waktu tunggu.
Tiga Opsi Eksekusi di Python #
Python menyediakan tiga jalur utama untuk program concurrent, dan ketiganya punya wilayah kerja masing-masing:
| Aspek | threading | multiprocessing | asyncio |
|---|---|---|---|
| Unit eksekusi | thread | process | coroutine |
| Dikelola oleh | sistem operasi | sistem operasi | event loop di program kita |
| Memori | dibagi dalam satu process | terpisah per process | satu thread, satu process |
| Biaya per unit | sedang | mahal | sangat murah |
| Wilayah kerja | I/O-bound | CPU-bound | I/O-bound berskala besar |
- threading (Chapter Threading): cocok untuk I/O-bound berukuran puluhan hingga ratusan pekerjaan, terutama yang memakai library blocking seperti
requests. - multiprocessing (Chapter Multiprocessing dan Process Pools): satu-satunya jalur yang melewati Global Interpreter Lock untuk kerja CPU-bound murni Python.
- asyncio: model berbasis coroutine untuk ribuan koneksi I/O bersamaan — dibahas tuntas pada Section Asynchronous Programming.
Dengan peta ini, kita siap masuk ke implementasi pertama: membuat thread sungguhan dan mengelola daur hidupnya.