Aplikasi modern menghabiskan sebagian besar waktunya bukan untuk berhitung, melainkan untuk menunggu: menunggu respons database, menunggu API eksternal, menunggu disk menulis berkas. Program synchronous yang menunggu akan membiarkan thread-nya menganggur terikat penuh pada satu pekerjaan, sehingga menangani 100 koneksi bersamaan berarti menyiapkan 100 thread seperti yang kita bahas pada Chapter Threading. Keyword async dan await menawarkan jalan lain: menunggu banyak operasi I/O sekaligus hanya dengan satu thread.
Masalah: Menunggu Itu Mahal #
Ketika interpreter Python mengeksekusi time.sleep(1) atau requests.get(...), thread yang menjalankan kode tersebut berhenti total sampai operasi selesai. Tidak ada baris lain di thread itu yang bisa berjalan. Operasi seperti ini disebut blocking I/O, dan biayanya timbul karena tiga hal:
- Setiap thread baru memakan memori untuk call stack, biasanya berukuran megabyte per thread.
- Sistem operasi harus menjadwalkan dan berpindah-pindah antar ratusan thread.
- Ribuan koneksi berarti ribuan thread, angka yang mulai tidak realistis untuk satu proses.
Premis asynchronous programming sederhana: daripada thread yang menunggu, kirimkan daftar “yang sedang ditunggu” ke satu penjadwal, lalu biarkan thread yang sama mengerjakan hal lain. Model ini disebut cooperative multitasking — task menyerahkan kontrol secara sukarela di titik-titik tertentu, bukan dipaksa oleh sistem operasi.
async def dan await #
Kata kunci async def mendefinisikan coroutine function. Aturan pentingnya: memanggil coroutine function tidak menjalankan isinya. Interpreter Python hanya membuat sebuah coroutine object dan mengembalikannya, sama seperti memanggil generator function pada Chapter yield dan Generator Functions. Baru keyword await yang menggerakkan coroutine tersebut sampai titik tunggu berikutnya.
import asyncio
async def ambil_nama():
return "Budi"
coro = ambil_nama()
print(type(coro))
coro.close()
print(asyncio.run(ambil_nama()))Output yang Dihasilkan:
<class 'coroutine'>
BudiMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 3 (
async def ambil_nama():): mendefinisikan coroutine function bernamaambil_nama. Function ini mengembalikan string saat dijalankan. - Baris 6 (
coro = ambil_nama()): memanggil coroutine function tanpaawait. Isi function belum dijalankan sama sekali; interpreter hanya membuat coroutine object dan menyimpannya ke variabelcoro. - Baris 7 (
print(type(coro))): mencetak<class 'coroutine'>— bukti bahwa hasil pemanggilan adalah sebuah object, bukan string"Budi". - Baris 8 (
coro.close()): menutup coroutine yang tidak jadi dijalankan supaya tidak memicu peringatan saat dibersihkan oleh garbage collector (kita bedah peringatan ini pada Chapter Coroutine dan Event Loop). - Baris 10 (
print(asyncio.run(ambil_nama()))): fungsiasyncio.run()menjalankan coroutine sampai selesai, lalu hasilnya"Budi"dicetak.
Dengan model mental ini, await sesuatu berarti: “jalankan sesuatu; jika sesuatu harus menunggu, serahkan kontrol ke penjadwal supaya task lain boleh maju.”
Demo: Berurutan vs Bersamaan #
Mari kita buktikan efeknya dengan pengukuran waktu nyata. Tiga “unduhan” disimulasikan dengan asyncio.sleep(1); versi pertama menunggunya satu per satu, versi kedua menunggu ketiganya sekaligus dengan asyncio.gather():
import asyncio
import time
async def unduh(nama):
print(f"{nama} mulai")
await asyncio.sleep(1)
print(f"{nama} selesai")
return nama
async def berurutan():
mulai = time.perf_counter()
await unduh("Budi")
await unduh("Siti")
await unduh("Ani")
print(f"total berurutan: {time.perf_counter() - mulai:.2f} detik")
async def bersamaan():
mulai = time.perf_counter()
await asyncio.gather(unduh("Budi"), unduh("Siti"), unduh("Ani"))
print(f"total bersamaan: {time.perf_counter() - mulai:.2f} detik")
async def main():
await berurutan()
await bersamaan()
asyncio.run(main())Output yang Dihasilkan (nilai total waktu dapat sedikit berbeda di mesin Anda):
Budi mulai
Budi selesai
Siti mulai
Siti selesai
Ani mulai
Ani selesai
total berurutan: 3.00 detik
Budi mulai
Siti mulai
Ani mulai
Budi selesai
Siti selesai
Ani selesai
total bersamaan: 1.00 detikMari kita telaah baris-baris kuncinya:
- Baris 4 (
async def unduh(nama):): coroutine function yang mensimulasikan satu operasi I/O selama satu detik. - Baris 6 (
await asyncio.sleep(1)): titik penyerahan kontrol. Saatunduh("Budi")menunggu di baris ini, coroutine lain boleh maju. - Baris 12-14 (
await unduh(...)): tigaawaitberurutan. Setiap unduhan menunggu unduhan sebelumnya tuntas, sehingga totalnya 3 detik. - Baris 19 (
await asyncio.gather(...)): menjadwalkan ketiga coroutine sebagai task dan menunggu semuanya sekaligus. Ketiganya menunggu bersamaan sehingga totalnya hanya 1 detik.
Satu Thread, Banyak Tugas #
Perhatikan urutan output versi gather: ketiga task mencetak “mulai” lebih dulu, baru mencetak “selesai”. Itulah wujud concurrency pada satu thread — task saling menyisip di titik await, bukan berjalan pada saat yang sama persis. Async tidak memberi kita parallelism untuk berhitung; async memberi kita cara menunggu banyak hal sekaligus tanpa biaya banyak thread.
Dengan model mental “await adalah titik menyerahkan kontrol”, kita siap membedah lebih dalam: apa sebenarnya coroutine object itu, dan siapa penjadwal yang mengatur pergantian task. Itulah tema Chapter Coroutine dan Event Loop.