2. Teori: Apa yang Sebenarnya Terjadi di Dalam Actor
2. Teori: Apa yang Sebenarnya Terjadi di Dalam Actor
Actor sering dijelaskan sebagai "class dengan mutex otomatis". Penjelasan itu tidak akurat dan menyebabkan kesalahpahaman saat menemui re-entrancy atau deadlock. Mari kita lihat model yang sebenarnya.
Actor = State + Serial Executor
Setiap actor instance memiliki dua komponen runtime:
- State — stored properties yang dilindungi oleh isolation.
- Executor — serial queue tempat semua method/property access actor dieksekusi.
Saat kamu memanggil await actor.method() dari konteks lain, runtime melakukan hop: pekerjaan dijadwalkan ke executor actor tersebut. Saat method selesai (atau menemui await lagi), pekerjaan bisa diserahkan kembali.
Caller context Actor's executor
| |
|---- enqueue work ---------->|
| | run synchronously
| | (no parallelism here)
|<--- result ----------------|
Properti penting yang sering disalahpahami:
- Actor bukan thread. Satu thread bisa menjalankan banyak actor; satu actor bisa berpindah antar thread (kecuali
@MainActor). - Actor menjamin serial execution di executor-nya, bukan "single-threaded affinity". Yang penting: tidak ada dua piece of work yang sama-sama mutate state di waktu yang sama.
- "Lock" yang dibuat actor adalah cooperative, bukan blocking. Saat menunggu akses, thread caller bisa mengerjakan task lain (work-stealing).
Synchronous vs Asynchronous Access
Akses dari dalam actor (self.foo) atau dari konteks yang sudah isolated ke actor itu = synchronous. Compiler tahu kita sudah di executor yang benar.
Akses dari luar actor = asynchronous. Harus await, karena harus hop ke executor target.
actor Counter {
var value = 0
func incrementAndRead() -> Int {
value += 1 // synchronous, sudah di executor
return read() // synchronous, internal call
}
func read() -> Int { value }
}
// Dari luar:
let c = Counter()
let v = await c.incrementAndRead() // await — hop ke executor Counter
Atomic Region (Synchronous Block)
Hal kunci yang sering dilewatkan: antara dua await, eksekusi actor bersifat atomic terhadap state-nya sendiri. Tidak ada method lain yang akan menyela mid-statement.
Tapi begitu kamu await di dalam method actor, executor dilepas, dan method lain bisa interleave. Inilah re-entrancy (Bagian 4).
actor Bank {
var balance = 100
func transfer(amount: Int, to other: Bank) async {
// ATOMIC region pertama
guard balance >= amount else { return }
balance -= amount // ini aman
await other.deposit(amount) // <-- executor DILEPAS di sini
// ATOMIC region kedua — `balance` mungkin sudah diubah
// oleh method lain yang interleave selama await di atas!
print("Balance now: \(balance)")
}
func deposit(_ amount: Int) { balance += amount }
}