← semua materi
05:00 Arsitektur Β· 01 Nov 2026 Β· 05:03

Model Konkurensi: Thread, Async, Actor, CSP

πŸ“š Topik: Empat paradigma konkurensi inti β€” preemptive threads, cooperative async/event loop, actor model, CSP β€” dan fondasi konseptual yang membedakannya.

🎯 Tujuan Belajar Setelah sesi ini: - Bisa pilih runtime konkurensi berdasarkan workload shape (CPU-bound vs IO-bound vs stateful-distributed), bukan berdasarkan hype bahasa. - Bisa jelaskan kenapa async tidak otomatis lebih cepat dari thread β€” dan kapan justru lebih lambat. - Bisa bedakan shared-memory + lock dari message-passing sebagai keputusan arsitektural, bukan style. - Bisa prediksi failure mode konkurensi sebelum ke production (race, deadlock, starvation, memory bloat, backpressure collapse).


🧠 Penjelasan Inti β€” Kenapa Ada Banyak Model?

Konkurensi = banyak unit eksekusi logis maju bersamaan. Pararelisme = benar-benar jalan serentak di core berbeda. Konkurensi soal struktur, pararelisme soal hardware. Satu program concurrent belum tentu parallel.

Tiga masalah fundamental yang harus dipecahkan setiap runtime konkurensi:

  1. Scheduling β€” siapa jalan sekarang? Siapa preempt siapa?
  2. Komunikasi / state sharing β€” unit eksekusi tukar data bagaimana tanpa korup?
  3. Blokir β€” saat nunggu IO, siapa yang dikorbankan?

Setiap model di bawah = jawaban berbeda untuk tiga pertanyaan itu. Tidak ada jawaban universal.


βš™οΈ Cara Kerja Internal

1. Preemptive Threads (OS Thread / Kernel Thread)

2. Async / Event Loop (cooperative, single-threaded core)

3. Actor Model (Erlang/OTP, Akka, Actix)

4. CSP β€” Communicating Sequential Processes (Go channels, occam, Clojure core.async)

Perbedaan Actor vs CSP (sering dikacaukan): | Aspek | Actor | CSP | |---|---|---| | Target pesan | Alamat actor (identity) | Channel (rendezvous point) | | Buffering | Mailbox tak terbatas (default) | Channel eksplisit, bisa unbuffered | | Coupling | Sender tahu receiver | Sender hanya tahu channel | | Blocking | Send biasanya non-blocking | Send bisa blocking (backpressure) | | Failure | Supervisor hierarchy | Manual (context, error channel) |


πŸ—οΈ Pseudocode β€” Sama Workload, Empat Model

# 1. THREADS (shared mem + lock)
data = {}; lock = Mutex()
def worker(k):
    v = fetch(k)          # blocking syscall, thread parkir
    lock.acquire(); data[k] = v; lock.release()
spawn N threads -> join        # N besar = memory/switch boom

# 2. ASYNC (single loop, cooperative)
async def worker(k):
    v = await fetch(k)    # suspensi di await, loop lanjut
    data[k] = v           # aman selama tidak ada await di sini
    # lock TIDAK wajib intra-loop, WAJIB lintas loop/thread
gather(worker(k) for k in keys)

# 3. ACTOR (mailbox, no shared state)
class Fetcher(Actor):
    state = {}
    def on_receive(msg):
        if msg == "fetch": v = fetch_io(msg.k); reply(msg.sender, v)
        if msg.starts_with("result"): state[msg.k] = msg.v
# banyak actor, mailbox queue, supervisor restart kalau crash

# 4. CSP (channel)
results = Channel()
def worker(k, out): out <- {k: fetch(k)}   # blocking send = backpressure
for k in keys: go worker(k, results)        # spawn goroutine
for _ in keys: merge(<-results)             # rendezvous

βš–οΈ Trade-off & Kapan JANGAN Dipakai

Model Pakai kalau JANGAN pakai kalau
Threads CPU-bound; butuh true parallel; library blocking warisan; jumlah unit kecil–sedang (< ratusan) 10K+ koneksi konkuren; IO-bound dominan; stack MB per unit = mematikan
Async IO-bound masif (10K+ koneksi); latency rendah; throughput koneksi tinggi Ada kode blocking tersembunyi (CPU heavy, lib sync) di jalur loop; tim belum paham cooperative trap; debug stack kompleks
Actor Stateful, long-lived entity; butuh fault isolation & distribute lintas node; sistem yang tumbuh jadi terdistribusi Operasi sederhana tanp state; butuh latensi super rendah per call; tim belum siap konsep supervisor/mailbox/dead letter
CSP Pipeline / fan-out fan-in / worker pool; orkestrasi aliran data; want composable concurrency Butuh alamat entity stateful yang hidup lama; butuh distribusi lintas node native (goroutine tidak pindah mesin)

Klarifikasi krusial β€” async β‰  cepat: async menang saat IO-bound karena 1 thread layani 10K koneksi parkir. Kalau workload CPU-bound, async malah merugikan: tasks serialized di core, no true parallel, plus overhead state machine. CPU-bound β†’ thread/process pool tetap jawaban. Banyak sistem hybrid: async untuk network IO + thread/process pool untuk CPU work (Go: goroutine + worker pool; Rust: tokio + spawn_blocking; Python: asyncio + run_in_executor).

Klarifikasi β€” thread model mana yang dipakai Go? Go = goroutine = konsep mirip CSP green thread (M:N scheduling: M goroutine ke N OS thread). Bukan OS thread 1:1. Preemptive sejak Go 1.14 (async preemption via signal) β€” jadi bukan cooperative murni lagi. Channel = primitive CSP. go keyword spawn, channel komunikasi. Tapi Go juga izinkan shared memory + mutex β€” jadi hybrid.

Rust: async = stackless coroutine (state machine di-compile, zero-cost abstraction). Tidak ada runtime default β€” pilih tokio/async-std/smol. No GC β†’ ownership model gantikan race detection runtime. Model = async + threads + channel (std::sync::mpsc, crossbeam, dan CSP-style via flume).


🏭 Contoh Produksi & Failure Mode

  1. Blocking di event loop (async) β€” Node.js: satu fs.readFileSync atau crypto hash di handler = seluruh server freeze untuk semua klien. Gejala: latency spike semua request sekaligus, CPU 100% di satu core. Fix: spawn_blocking / worker thread / pindah ke native async API.

  2. Thread explosion β€” Java/C#: request-scoped thread tanpa pool. Traffic naik β†’ ribuan thread blocking β†’ OOM + massive context switch. Fix: thread pool + async IO, atau virtual threads (Java 21 Loom) / goroutine-style.

  3. Deadlock lock ordering β€” Dua thread acquire lock A lalu B vs B lalu A. Klasik, masih terjadi di 2026. Deteksi: jstack, Go -race/deadlock detector, Rust belum ada runtime deadlock detect. Fix: global lock ordering, atau hindari shared mutable state.

  4. Mailbox unbounded growth (Actor) β€” Producer lebih cepat dari consumer, mailbox tak dibatasi β†’ memory bloat β†’ akhirnya OOM. Ini tidak ada backpressure by default di actor model. Erlang/Akka punya mailbox limit (harus diset). Pelajaran: default mailbox unbounded = bug latency.

  5. Bounded queue jadi bottleneck (CSP) β€” channel pendek + consumer lambat β†’ sender blocking β†’ throughput turun, latency naik. Ini mau tidak mau backpressure β€” bukan bug, tapi harus dipahami sebagai choke point. Unbuffered channel = full rendezvous = sinkron penuh.

  6. Priority inversion / starvation (async) β€” Task CPU-heavy diselipkan di loop β†’ task lain starvation karena tidak ada preemption. Cooperative model balas dendam.

  7. Jones teknis: Erlang "let it crash" β€” actor crash bukan bencana kalau supervisor desain benar. Di thread/shared-memory, crash satu unit jatuhkan semuanya (whole process). Ini alasan fault-isolation actor model mahal nilai di sistem uptime tinggi (telekomunikasi, WhatsApp lama).


⚠️ Miskonsepsi Umum

  1. "Async lebih cepat" β€” Salah sebagai pernyataan umum. Async menambah throughput untuk IO-bound konkuren. Untuk CPU-bound, lebih lambat (no parallelism + overhead state machine). Selalu tanya: workload shape apa?

  2. "Threads itu obsolete" β€” Salah. CPU-bound, blocking lib warisan, dan true parallelism tetap butuh thread. Async tidak menggantikan thread; ia melengkapi. Banyak stack produksi jalan keduanya.

  3. "Actor otomatis skala ke mana saja" β€” Salah. Actor local gratis; actor remote kena network partition, serialization cost, at-least-once delivery, ordering loss antar node. "Sama" hanya di API, bukan di failure semantics.

  4. "Channel replay" / "Actor bebas race" β€” Salah. Tanpa shared state, race state hilang di dalam actor, tapi race antar actor tetap ada (urutan pesan, akses resource eksternal seperti DB). Concurrency safety β‰  distributed safety.

  5. "Cooperative = mudah debug" β€” Terbalik. Async stack trace terfragmentasi (stack tersebar di heap sebagai continuation), sulit dibaca. Thread stack lebih intuitif. Trade-off: async hemat resource, tapi debug mahal.

  6. "Go preemptive seperti OS" β€” Setengah benar sejak 1.14 ada async preemption, tapi bukan sekuat OS scheduler. Potongan loop tak terpreempt (asm/cgo) tetap bisa stall. Dan izinkan blocking syscall β†’ runtime detach M, jadi tidak sama dengan cooperative murni.


βœ… Cek Pemahaman

  1. Server kamu 10K koneksi WebSocket idle, tiap koneksi sesekali kirim pesan. Throughput rendah, konkurensi tinggi. Model mana? Kenapa thread model pilihan buruk di sini?
  2. Service kamu hitung hash Argon2 per request (CPU-heavy 100ms). Kamu pakai Node.js async. Apa yang terjadi di bawah beban? Apa fix arsitekturnya?
  3. Kamu desain sistem dengan 1 juta entitas stateful (game/chat) yang hidup lama, butuh restart independen saat error. Actor atau CSP? Justifikasi pakai failure model, bukan preferensi idiomatik.

πŸ”‘ Poin Kunci


➑️ Eksplorasi Lanjut

  1. Structured concurrency β€” Kotlin coroutines, Swift structured concurrency, Python TaskGroup (3.11+), Java Loom StructuredTaskScope. Kenapa "task tak punya parent" = source leak & error swallowing?
  2. Virtual threads (Java 21 Loom) & green threads β€” M:N scheduling di runtime, bukan OS. Trade-off vs event loop.
  3. Backpressure sebagai first-class design β€” bounded queue, reactive streams (Reactive Streams spec), Kafka consumer lag, load shedding. Sambung ke saga & event-driven.
  4. Consistency model konkurensi β€” memory model bahasa (C++11 memory model, Java JMM happens-before). Kenapa volatile / atomic ordering bukan detail mikro.
  5. Runtime konkurensi lintas bahasa β€” tokio (Rust), asyncio (Python), libuv (Node), GMP (Go), BEAM (Erlang). Bandingkan scheduling & preemption.

Konsep fundamental; tidak ada data versi terikat waktu yang rentan berubah. Verifikasi versi spesifik runtime (Loom, tokio, Go preemption) dilakukan saat implementasi.