Section 2/172 menit

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:

  1. State — stored properties yang dilindungi oleh isolation.
  2. 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.

swift
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.

swift
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).

swift
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 }
}