- EN
- ID
Panduan ini untuk siapa
- Pembelajar yang membangun program untuk menangani banyak tugas sekaligus
- Developer yang menentukan pilihan antara threads, processes, dan pendekatan async
- Engineer yang mengoptimalkan beban kerja I/O-heavy atau CPU-heavy
Apa yang akan Anda pelajari
- Perbedaan concurrency dan parallelism
- Bagaimana GIL memengaruhi threading di CPython
- Kapan menggunakan
threading,multiprocessing, danasyncio - Cara kerja
async/awaitdan event loop dalam praktik - Panduan pemilihan praktis untuk workload nyata
Mengapa topik ini penting
Aplikasi modern sering melakukan banyak operasi sekaligus: network call, file I/O, background job, dan user request. Memilih model eksekusi yang salah bisa membuang resource atau membuat kode jadi tidak perlu rumit.
Python menyediakan beberapa opsi concurrency, masing-masing punya kelebihan dan trade-off. Memahami kapan memakai tiap pendekatan membantu Anda membangun sistem yang responsif dan efisien.
Konsep inti
Concurrency vs parallelism dan GIL
Concurrency berarti mengelola banyak tugas sepanjang waktu. Parallelism berarti tugas dieksekusi pada saat yang sama di banyak core.
Di CPython, Global Interpreter Lock (GIL) hanya mengizinkan satu thread mengeksekusi Python bytecode pada satu waktu. Ini terutama penting untuk tugas CPU-bound.
Aturan praktis:
- Tugas I/O-bound -> threads atau asyncio
- Tugas CPU-bound -> multiprocessing
Threading dan multiprocessing
Threading cocok untuk tugas menunggu (network, disk, API).
import threading
import time
def task(name):
time.sleep(1)
print(f"Done: {name}")
threads = [threading.Thread(target=task, args=(f"t{i}",)) for i in range(3)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
Multiprocessing menjalankan process terpisah dan bisa memakai banyak CPU core untuk komputasi berat.
Asyncio untuk I/O concurrency tinggi
asyncio berbasis event loop dan unggul saat banyak tugas I/O perlu dikoordinasikan secara efisien.
import asyncio
async def fetch(name, delay):
await asyncio.sleep(delay)
return f"{name} done"
async def main():
results = await asyncio.gather(
fetch("A", 1),
fetch("B", 1),
fetch("C", 1),
)
print(results)
asyncio.run(main())
Pendekatan ini menghindari pembuatan banyak thread untuk skenario high I/O concurrency.
Panduan langkah demi langkah
Langkah 1 — Klasifikasikan jenis workload terlebih dahulu
Sebelum menulis concurrency, identifikasi biaya dominan:
- Menunggu network/file -> I/O-bound
- Komputasi numerik berat -> CPU-bound
Satu keputusan ini menentukan pilihan tool.
Langkah 2 — Implementasi versi concurrent kecil
Untuk workload mirip I/O wait, bandingkan sequential vs concurrent.
import time
import asyncio
async def io_task():
await asyncio.sleep(1)
async def run_concurrent():
await asyncio.gather(*(io_task() for _ in range(5)))
start = time.perf_counter()
asyncio.run(run_concurrent())
print(f"Elapsed: {time.perf_counter() - start:.2f}s")
Anda biasanya akan melihat runtime mendekati satu detik, bukan lima.
Langkah 3 — Tambahkan safety dan observability
Concurrency menambah kompleksitas. Tambahkan:
- Timeouts
- Error handling per task
- Logging/task naming
Reliability sama pentingnya dengan peningkatan kecepatan.
Contoh praktis
Contoh 1 — CPU-bound dengan multiprocessing
from multiprocessing import Pool
def square(n):
return n * n
if __name__ == "__main__":
with Pool() as pool:
print(pool.map(square, [1, 2, 3, 4, 5]))
Expected output:
[1, 4, 9, 16, 25]
Pola ini menskalakan tugas CPU lebih baik daripada thread pada banyak kasus CPython.
Contoh 2 — Penanganan timeout pada task async
import asyncio
async def slow():
await asyncio.sleep(3)
return "done"
async def main():
try:
result = await asyncio.wait_for(slow(), timeout=1)
print(result)
except asyncio.TimeoutError:
print("Task timed out")
asyncio.run(main())
Expected output:
Task timed out
Contoh 3 — Snapshot perbandingan performa (workload mirip I/O)
import asyncio
import threading
import time
def io_sleep():
time.sleep(1)
def run_sequential():
start = time.perf_counter()
for _ in range(5):
io_sleep()
return time.perf_counter() - start
def run_threading():
start = time.perf_counter()
threads = [threading.Thread(target=io_sleep) for _ in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
return time.perf_counter() - start
async def run_asyncio():
start = time.perf_counter()
await asyncio.gather(*(asyncio.sleep(1) for _ in range(5)))
return time.perf_counter() - start
seq_time = run_sequential()
thr_time = run_threading()
async_time = asyncio.run(run_asyncio())
print(f"Sequential: {seq_time:.2f}s")
print(f"Threading : {thr_time:.2f}s")
print(f"Asyncio : {async_time:.2f}s")
Expected output (typical):
Sequential: ~5.00s
Threading : ~1.00s
Asyncio : ~1.00s
Perbandingan sederhana ini menunjukkan mengapa strategi concurrency harus sesuai jenis workload.
Kesalahan umum dan cara menghindarinya
- Memakai thread untuk pekerjaan CPU-heavy di CPython -> Utamakan multiprocessing untuk tugas CPU paralel.
- Mencampur blocking call di dalam function async -> Gunakan library async-compatible dan hindari operasi blocking di event loop.
- Menambahkan concurrency sebelum mengukur bottleneck -> Profile dulu, lalu optimasi area yang tepat.
- Mengabaikan cancellation/timeouts -> Tambahkan jalur timeout dan cancellation demi ketahanan.
Latihan cepat
- Implementasikan satu tugas I/O secara sequential dan dengan
asyncio.gather, lalu bandingkan runtime. - Implementasikan function CPU-heavy dan uji dengan multiprocessing pool.
- Tambahkan timeout handling ke operasi async dan catat event timeout.
Ringkasan utama
- Pilih model concurrency berdasarkan jenis workload, bukan tren.
- Threads dan asyncio cocok untuk tugas I/O-bound; multiprocessing cocok untuk tugas CPU-bound.
- GIL adalah constraint desain penting untuk perilaku thread di CPython.
- Concurrency membutuhkan error handling dan observability yang kuat agar aman di production.
Langkah berikutnya
Lanjut ke Web Frameworks & Pengembangan Aplikasi. Di panduan berikutnya, Anda akan membandingkan framework Python utama dan memilih yang paling sesuai dengan jenis aplikasi Anda.
No Comments