Banyak developer memakai istilah concurrency dan parallelism bergantian seolah bermakna sama. Akibatnya, diskusi tentang coroutines sering rancu: apakah “coroutine berjalan paralel” atau “coroutine berjalan konkuren”? Chapter ini memisahkan dua konsep tersebut secara presisi dan menunjukkan bagaimana coroutines Kotlin dapat memenuhi kedua model — concurrency tanpa parallelism, concurrency dengan parallelism, hingga parallelism tanpa concurrency. Pemahaman ini menjadi landasan untuk memilih dispatcher yang tepat pada chapter berikutnya.
Definisi Operasional #
Concurrency adalah kemampuan sebuah program untuk menangani banyak tugas pada saat yang bersamaan secara logis. Tugas-tugas tersebut tidak harus berjalan serentak secara fisik — yang penting program mampu beralih di antara tugas dan membuat kemajuan pada semuanya. Sebuah thread tunggal yang melayani banyak koneksi IO secara bergantian adalah contoh concurrency murni.
Parallelism adalah kemampuan untuk menjalankan banyak tugas pada saat yang bersamaan secara fisik. Hal ini memerlukan beberapa unit eksekusi (CPU core, thread OS). Sebuah program yang membagi komputasi numerik ke empat core sehingga masing-masing menghitung seperempat data adalah contoh parallelism.
Perbedaan inti dirangkum pada tabel berikut:
| Aspek | Concurrency | Parallelism |
|---|---|---|
| Unit | Logis (struktur program) | Fisik (eksekusi simultan) |
| Butuh banyak core? | Tidak | Ya, untuk parallelism sejati |
| Tujuan utama | Menangani banyak tugas (IO, event) | Mempercepat komputasi CPU-bound |
| Contoh di Kotlin | async/launch tanpa thread baru | Dispatchers.Default pada multi-core |
Rob Pike menyatakan dengan ringkas: concurrency is about dealing with lots of things at once; parallelism is about doing lots of things at once.
Concurrency pada Satu Thread #
Coroutines tidak memerlukan banyak thread untuk bersifat konkuren. Sebuah runBlocking pada main thread dapat menjalankan beberapa async yang masing-masing menangguhkan diri pada titik delay. Selama penangguhan, thread utama bebas mengeksekusi coroutine lain.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("== Concurrency pada 1 thread ==")
// Tiga coroutine dijalankan secara konkuren tanpa parallelism
val a = async(Dispatchers.Unconfined) { cetakDanKembalikan("A", 1) }
val b = async(Dispatchers.Unconfined) { cetakDanKembalikan("B", 2) }
val c = async(Dispatchers.Unconfined) { cetakDanKembalikan("C", 3) }
println("hasil: ${a.await()} ${b.await()} ${c.await()}")
println()
println("== Parallelism pada banyak thread ==")
// Empat async di Dispatchers.Default berjalan paralel
val tugas = (1..4).map { i ->
async(Dispatchers.Default) {
delay(200)
i * 10
}
}
val total = tugas.awaitAll().sum()
println("total=$total")
}
suspend fun cetakDanKembalikan(label: String, nilai: Int): Int {
println(" -> $label mulai")
delay(50)
println(" <- $label selesai")
return nilai
}Output yang Dihasilkan:
== Concurrency pada 1 thread ==
-> A mulai
-> B mulai
-> C mulai
<- A selesai
<- B selesai
<- C selesai
hasil: 1 2 3
== Parallelism pada banyak thread ==
total=100Mari kita telaah baris-baris kode di atas:
- Baris 4-8 (blok concurrency): tiga
asyncdijadwalkan berurutan denganDispatchers.Unconfined. Saat sebuahasyncdijadwalkan, ia langsung berjalan di thread pemanggil sampai menemui titik suspend (delay(50)). Setelah suspend, eksekusi kembali kerunBlockingdan coroutine berikutnya dimulai. - Pola output “mulai A, mulai B, mulai C, selesai A, selesai B, selesai C”: membuktikan ketiga coroutine berjalan bersamaan secara logis. Mereka memulai hampir bersamaan lalu selesai hampir bersamaan — semua pada satu thread
main. - Baris 12-17 (blok parallelism):
async(Dispatchers.Default)memindahkan eksekusi ke thread pool bawaan (biasanya jumlahnya sama dengan core CPU). Empat tugas dengandelay(200)dijadwalkan paralel. awaitAll().sum(): menunggu semua hasil lalu menjumlahkan. Karena paralel, total durasi mendekati 200ms (bukan 800ms). Output yang dicetak hanyalahtotal=100karenadelaytidak menghasilkan output antara.Dispatchers.Unconfined: dipakai khusus untuk demo concurrency pada satu thread. Dispatcher ini tidak memindahkan eksekusi ke thread lain — coroutine tetap di thread pemanggil. Untuk kode produksi, gunakan dispatcher lain yang akan kita bahas pada chapter Dispatchers.
Tiga Kombinasi yang Mungkin #
Pemahaman concurrency dan parallelism menghasilkan tiga kombinasi praktis:
| Kombinasi | Contoh | Catatan |
|---|---|---|
| Concurrency tanpa parallelism | UI thread + banyak async | Cocok untuk aplikasi UI; thread utama tetap responsif |
| Concurrency dengan parallelism | Multi-platform server dengan Dispatchers.Default | Maksimalisasi throughput CPU + IO |
| Parallelism tanpa concurrency | Worker thread pool memproses antrian tugas | Cocok untuk batch processing data |
Coroutine Kotlin mendukung ketiganya dengan satu model kode — yang berubah hanya pemilihan dispatcher dan struktur scope. Tidak ada perubahan sintaksis: async tetap async, await tetap await.
Jebakan Umum #
Sering dianggap bahwa “lebih banyak coroutine = lebih cepat”. Kenyataannya, jika semua coroutine memakai Dispatchers.Main (satu thread), tidak ada parallelism sama sekali — hanya concurrency. Kode CPU-bound seperti sorting atau parsing JSON besar tidak akan dipercepat dengan menambah coroutine pada dispatcher Main; sebaliknya, ia justru melambat karena overhead penjadwalan.
Referensi Lanjutan #
- Kotlin coroutines overview — posisi coroutine dalam model konkurensi Kotlin
- Coroutine context and dispatchers — dispatcher menentukan thread pool tempat coroutine dijadwalkan
Selanjutnya #
Setelah memahami concurrency dan parallelism, langkah berikutnya adalah melihat langsung konstruksi dasar coroutines: runBlocking, launch, dan await — yang akan kita bedah pada chapter Coroutine Fundamentals.