Belajar dengan membaca secara berurutan

Dasar async / await — Mempercepat Program dengan Memanfaatkan Waktu Tunggu

Pelajari mekanisme coroutine, event loop, dan asyncio.sleep, lalu cara async def / await mengalihkan waktu tunggu I/O ke task lain dan mempercepat program.

async / await adalah mekanisme untuk mengerjakan pekerjaan lain selagi menunggu I/O (Input/Output — pembacaan file, panggilan jaringan, query DB, dan operasi yang didominasi oleh waktu tunggu lainnya). Mekanisme ini bisa mempercepat tugas seperti memanggil Web API 100 kali, tanpa keluar dari satu thread. Artikel ini membahas tiga ide inti: coroutine, event loop, dan asyncio.sleep.

Tentang menjalankan kode di sini

async / await berfokus pada timing, tetapi runner di situs ini menyangga output print dan menampilkannya sekaligus setelah skrip selesai. Output real-time dan rasa waktu yang berlalu tidak akan sama dengan environment Python asli. Artikel ini menggunakan diagram untuk memperjelas perilaku internalnya, tetapi jika kamu ingin melihat aliran print secara langsung atau merasakan timing yang sebenarnya, jalankan di environment Python lokalmu dengan asyncio.run.

Di mana async / await bersinar
Panggil 100 Web APIsecara konkurenJalankan query DBsecara konkurenMemproses I/O filesecara paralelWeb scrapingbanyak halaman sekaligus
Paling cocok untuk pekerjaan yang didominasi waktu tunggu. async beralih ke task lain saat CPU menganggur (menunggu I/O / jaringan / sleep), menjaga program tetap di satu thread sambil membuat keseluruhan jadi lebih cepat.

Process dan Thread — konteks untuk async / await

Sebelum masuk ke async / await, mari kita pastikan dulu apa itu process dan thread.

Process (satu program yang berjalan dari sudut pandang OS) adalah unit eksekusi independen dengan memory space sendiri. Jalankan satu skrip Python dan kamu mendapatkan satu process; jalankan satu lagi di terminal berbeda dan kamu memiliki dua.

Thread (alur yang sebenarnya menjalankan kode di dalam process) adalah unit yang memakai CPU untuk memajukan kode. Sebuah process bisa memiliki banyak thread, tetapi program Python biasa berjalan sebagai 1 process + 1 thread, dan menunggu seperti time.sleep menghentikan thread tersebut.

Process / Thread / Coroutine — lapisan bersarang
Process (satu aplikasi Python)
  • Unit eksekusi independen dari sudut pandang OS
  • Memiliki memory space sendiri
Thread (alur eksekusi)
  • Unit yang benar-benar menjalankan kode di dalam process
  • Python biasa umumnya hanya memiliki satu thread yang berjalan
Coroutine (fungsi async def)
  • Bergantian giliran di dalam satu thread
  • Kamu bisa membuat sebanyak yang kamu mau dengan async / await
Tiga level penyusunan: process memuat thread, yang memuat coroutine. async / await hanya menjadwalkan coroutine paling dalam — tidak pernah menambah thread atau CPU. Itulah perbedaan utamanya dengan threading (menambah thread) dan multiprocessing (menambah process).

async / await hanya menangani coroutine paling dalam. Mekanisme ini berpindah antar coroutine pada thread yang sama, tanpa pernah menambah thread atau CPU — ia hanya menyerahkan kontrol ke coroutine lain saat CPU menganggur menunggu I/O. Untuk benar-benar menjalankan thread secara paralel, gunakan threading; untuk menambah process, gunakan multiprocessing (dibahas di dua artikel berikutnya).

Apa itu coroutine — bintang dari async / await

Coroutine (fungsi / objek yang didefinisikan dengan async def yang bisa berhenti sementara di titik await internal) adalah inti dari async / await. Fungsi biasa berjalan lurus sampai akhir saat dipanggil, tetapi coroutine bisa berhenti sementara di setiap await internal dan melanjutkan ketika event loop menyuruhnya. Itulah fondasi yang menjadi dasar async.

Memanggil fungsi async def tidak menjalankan body-nya — ia hanya mengembalikan objek coroutine (misalnya, coro = hello()). Body baru mulai berjalan saat kamu await coro atau meneruskannya ke asyncio.run(coro).

import asyncio

async def hello():
    return "Hi"

# Memanggilnya tidak menjalankan body
coro = hello()
print(coro)             # <coroutine object hello at 0x...>
                        # ↑ 0x... adalah alamat memori.
                        #   Itu hanya berarti "sebuah coroutine telah dibuat"

# await yang sebenarnya menjalankannya
print(await coro)       # Hi
State coroutine
async def f()didefinisikanf() dipanggilcoroutine dibuatawait f()→ mulai berjalanreturn→ selesai
Memanggil fungsi async def tidak menjalankan body — ia hanya mengembalikan objek coroutine. await yang sebenarnya memulai eksekusi, dan return mengakhirinya. Titik await internal berhenti sementara dan melanjutkan di antaranya.

Saat kamu menulis await some_io(), coroutine berhenti sementara dan beralih ke task lain, dan coroutine lain bisa berjalan selagi coroutine ini menunggu — itulah fitur khas async.

Apa itu event loop — scheduler yang menggilir coroutine

Event loop (scheduler yang menggilir coroutine bergantian) adalah yang dijalankan asyncio di balik layar. Ia berputar tanpa henti: ambil coroutine dari antrean → jalankan → beralih ke yang lain di await → kembalikan yang penantiannya selesai ke antrean.

import asyncio

async def main():
    print("start")
    await asyncio.sleep(0)   # sleep(0) tidak benar-benar menunggu,
                             #   ia hanya menandai "boleh beralih di sini"
    print("end")

# asyncio.run memulai loop → menjalankan main → menutup loop saat selesai
asyncio.run(main())
# Output:
# start
# end
Cara kerja event loop
Ambil dariantreanJalankancoroutineSaat await,beralih keluarI/O selesai →kembali ke antrean
Event loop menarik coroutine yang siap jalan dari antrean dan menjalankannya, beralih ke coroutine lain saat satu mencapai await, dan mengembalikan coroutine ke antrean saat I/O-nya selesai — berputar selamanya di satu thread.

Semua ini terjadi di satu thread — tanpa core CPU tambahan, hanya mengisi waktu tunggu yang menganggur dengan coroutine lain. Di Python berbasis browser di situs ini, event loop sudah berjalan, sehingga kamu bisa menulis await langsung di top level tanpa memanggil asyncio.run(...).

Kenapa async / await — beralih ke task lain saat menunggu

Di kode sinkron (sync) (gaya baris demi baris yang biasa), time.sleep(1) memblokir program — CPU menganggur, tetapi program membeku. Hal yang sama berlaku untuk respons Web API, query DB, dan penyelesaian I/O file.

async / await memungkinkan kamu beralih ke task lain di mana pun kamu menulis "tunggu", tetap di thread yang sama tetapi beralih saat penantian dimulai.

# requests = HTTP client sinkron (satu panggilan dalam satu waktu)
# httpx    = HTTP client yang mendukung async (await panggilan secara paralel)
import requests, asyncio, httpx

# Sync: panggil 3 API satu per satu → total 3 detik
def fetch_users_sync():
    r1 = requests.get("https://api.example.com/users/1")  # ← menunggu 1 detik
    r2 = requests.get("https://api.example.com/users/2")  # ← 1 detik lagi
    r3 = requests.get("https://api.example.com/users/3")  # ← 1 detik lagi
    return [r1.json(), r2.json(), r3.json()]

# Async: panggil 3-nya secara paralel → selesai dalam ~1 detik (yang paling lambat)
async def fetch_users_async():
    async with httpx.AsyncClient() as client:
        r1, r2, r3 = await asyncio.gather(
            client.get("https://api.example.com/users/1"),
            client.get("https://api.example.com/users/2"),
            client.get("https://api.example.com/users/3"),
        )
        return [r1.json(), r2.json(), r3.json()]

Detail asyncio.gather menyusul

asyncio.gather(...) yang dipakai di sini menjalankan beberapa coroutine secara konkuren dan menunggu semua hasilnya. Semantik detailnya — nilai kembalian, penanganan exception — dibahas di artikel berikutnya: asyncio Tasks.

async — beberapa task bergantian
A berjalanB menungguC menungguA: await→ beralih keluarB berjalanC menungguA menungguB: await→ beralih keluarC berjalanberalihberalih
Tiga coroutine berbagi satu CPU di thread yang sama. Setiap task beralih keluar di await saat mulai menunggu, dan yang berikutnya berjalan. Waktu tunggu maju secara paralel tanpa pernah keluar dari thread tunggal — itulah inti async.
Concurrent vs Parallel
ConcurrentBeralih antartask di satu CPUasync / await(artikel ini)ParallelBanyak core CPUberjalan bersamaanthreading /multiprocessing(artikel berikutnya)
Concurrent = beralih antar task di satu CPU (yang diberikan async / await). Parallel = banyak core CPU benar-benar berjalan bersamaan (dicapai dengan threading / multiprocessing). async tidak akan mempercepat kode yang memakai CPU 100%.

Concurrent, bukan parallel

async / await tidak pernah menambah CPU — kode yang memaksimalkan CPU (komputasi berat) tidak akan dipercepat. Ia hanya mengisi waktu CPU yang menganggur saat "menunggu I/O / menunggu jaringan / sleep" dengan beralih ke task lain. Ini eksekusi concurrent, bukan eksekusi parallel sejati. Untuk benar-benar berjalan di banyak core, kamu perlu threading atau multiprocessing — dibahas di dua artikel berikutnya.

async def dan await — dasarnya

Fungsi yang didefinisikan dengan async def disebut fungsi coroutinememanggilnya tidak menjalankan body, hanya mengembalikan objek coroutine. Untuk benar-benar menjalankannya, await ia atau teruskan ke asyncio.run().

await x berarti "tunggu x selesai, sambil beralih ke task lain di sela-sela". x bisa salah satu dari tiga hal: coroutine (fokus artikel ini), Task, atau Future (objek Task yang dibahas di artikel berikutnya, plus objek pemberitahuan penyelesaian yang dipakainya secara internal) — dalam kode sehari-hari, kamu kebanyakan akan memakai coroutine atau Task.

import asyncio

# Definisikan fungsi coroutine dengan async def
async def hello():
    return "Hi"

# Memanggilnya hanya mengembalikan objek coroutine (body tidak berjalan)
print(hello())                    # <coroutine object hello at 0x...>

# await menjalankannya (top-level await bekerja di environment browser ini)
result = await hello()
print(result)                     # Hi

# Di skrip Python asli, bungkus dengan asyncio.run()
# print(asyncio.run(hello()))     # Hi
async def dan await
async def hello(): return 'Hi'hasil hello()= sebuah coroutine(body tidak jalan)await hello()→ kembalikan 'Hi'panggil sajaawait
Definisikan dengan async defmemanggil saja mengembalikan objek coroutine dengan body belum dieksekusi → await yang benar-benar menjalankannya dan menghasilkan hasil. Itulah aturan minimum async / await.
ElemenArtiCatatan
async def f():Mendefinisikan fungsi coroutineMemanggilnya tidak menjalankan body
f()Membuat objek coroutineButuh await untuk benar-benar berjalan
await f()Menunggu penyelesaian, sambil beralih ke yang lainHanya legal di dalam fungsi async
asyncio.sleep(N)Tunggu N detik (memberikan giliran selama menunggu)Tidak memblokir seperti time.sleep
asyncio.run(f())Berjalan dari top levelEntry point standar di Python asli

Tanpa await, coroutine tidak akan pernah berjalan

Jika kamu menulis hello() saja, body tidak akan pernah berjalan, dan kamu akan melihat peringatan seperti <coroutine object hello at 0x...> di console. Selalu await hello() atau jalankan via asyncio.run(hello()). "Memanggil" dan "menjalankan" adalah dua hal berbeda di async — bedakan keduanya.

Tulis fungsi async kecil yang mensimulasikan panggilan API, lalu jalankan dengan await. Kita akan menggunakan asyncio.sleep(0.5) untuk memalsukan waktu respons 0.5 detik.

① Tambahkan import asyncio.

② Definisikan async def fetch_user(user_id): — print f"user {user_id} start", await asyncio.sleep(0.5) untuk menunggu 0.5 detik, print f"user {user_id} done", lalu return f"User{user_id}".

③ Jalankan dengan result = await fetch_user(1) dan simpan nilainya (top-level await bekerja di sini).

④ Print f"result: {result}".

(Jalankan dengan sukses dan penjelasan akan muncul.)

Python Editor

Jalankan kode untuk melihat output

Eksekusi konkuren membagi penantian — keuntungan asyncio

asyncio.sleep(seconds) adalah versi async dari sleep — ia beralih ke task lain selama menunggu. Dikombinasikan dengan asyncio.gather (artikel berikutnya) untuk menjalankan beberapa coroutine sekaligus, semua sleep berjalan secara konkuren, sehingga total waktunya menjadi sleep tunggal terlama (bukan jumlahnya, seperti yang akan diberikan time.sleep).

Secara internal, asyncio.sleep(N) mendaftarkan "lanjutkan coroutine ini dalam N detik" ke loop dan langsung beralih keluar. Sebaliknya, time.sleep(N) adalah panggilan OS yang sepenuhnya memblokir CPU — loop juga berhenti, dan tidak ada coroutine lain yang bisa berjalan. Memakai time.sleep di dalam fungsi async menggagalkan tujuan utama async, jadi hati-hati.

Di dalam asyncio.sleep
awaitasyncio.sleep(1)Beri tahu loop:"bangunkan dalam 1d"Sementara itu coroutinelain berjalan1 detik kemudian→ lanjutkan
asyncio.sleep(N) mendaftarkan "bangunkan dalam N detik" ke event loop dan beralih keluar. Selama jendela waktu itu, coroutine lain berjalan. Setelah N detik, coroutine kembali ke antrean dan melanjutkan.
time.sleep vs asyncio.sleep
time.sleep(1)(sync)Memblokir CPUselama 1 detikTask async lainjuga membekuasyncio.sleep(1)(async)Menyerahkan ke loopselama 1 detikTask lainbisa berjalan
time.sleep memblokir CPU sepenuhnya, sehingga task async lain tidak bisa berjalan. asyncio.sleep beralih ke task lain, membuat mereka bisa maju — selalu pakai asyncio.sleep di dalam fungsi async.

Panggil fetch_user yang sama 3 kali secara paralel dan saksikan pekerjaan sekuensial 1.5 detik selesai dalam 0.5 detik. Kita memakai asyncio.gather untuk eksekusi paralel (detail lengkapnya di artikel berikutnya).

① Tambahkan import asyncio.

② Definisikan async def fetch_user(user_id): — sama dengan latihan sebelumnya (print start → await asyncio.sleep(0.5) → print done → return).

③ Jalankan dengan results = await asyncio.gather(fetch_user(1), fetch_user(2), fetch_user(3)) untuk memicu 3 panggilan secara paralel.

④ Print list hasilnya sebagai f"results: {results}".

Python Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Diberikan async def hello(): return 'Hi', apa yang dikembalikan hello()?

Soal 2Di dalam fungsi async, mana yang "menunggu N detik sambil beralih ke task lain"?

Soal 3Workload mana yang paling diuntungkan oleh async / await?