Guarded Suspension Pattern #

Dalam pemrograman concurrent dan parallel, salah satu tantangan terbesar adalah sinkronisasi antar goroutine atau thread. Banyak proses tidak bisa langsung dijalankan karena bergantung pada kondisi tertentu—misalnya data belum tersedia, resource masih digunakan, atau event tertentu belum terjadi.

Jika kondisi ini tidak ditangani dengan benar, kita bisa berakhir dengan busy waiting, race condition, atau deadlock. Di sinilah Guarded Suspension Pattern berperan. Pattern ini memungkinkan sebuah task menunggu dengan aman sampai kondisi yang dibutuhkan terpenuhi, tanpa memboroskan CPU dan tanpa merusak konsistensi data.

Pattern ini sangat relevan di dunia Go (Golang), karena Go menyediakan primitive concurrency yang kuat seperti mutex, condition variable, dan channel.

Apa itu Guarded Suspension Pattern? #

Guarded Suspension Pattern adalah concurrency pattern di mana sebuah thread atau goroutine:

  1. Mengecek sebuah guard condition (kondisi penjaga)
  2. Jika kondisi belum terpenuhi, eksekusi akan disuspend / ditunda
  3. Akan dilanjutkan kembali ketika kondisi tersebut terpenuhi

Secara konseptual, polanya bisa digambarkan sebagai:

“Jalankan operasi hanya jika kondisi terpenuhi, jika tidak—tunggu sampai kondisi itu benar.”

Pattern ini biasanya diimplementasikan menggunakan:

  • Mutex + Condition Variable
  • Channel (di Go)
  • Monitor Object (di bahasa lain seperti Java)

Tujuan Pattern #

Tujuan utama Guarded Suspension Pattern adalah:

  • Menghindari busy waiting (loop cek kondisi terus-menerus)
  • Menjaga thread safety saat mengakses shared resource
  • Mengkoordinasikan eksekusi antar thread/goroutine
  • Membuat kode concurrent lebih deterministik dan mudah dipahami

Pattern ini membantu memastikan bahwa sebuah operasi hanya dijalankan ketika state sistem sudah valid.


Kapan Cocok Digunakan? #

Guarded Suspension Pattern cocok digunakan ketika:

  • Sebuah proses bergantung pada state tertentu
  • Producer–Consumer problem
  • Job queue / task queue
  • Resource pool (connection pool, worker pool)
  • Event-driven system
  • Thread perlu menunggu hasil dari thread lain

Tidak cocok digunakan jika:

  • Kondisi bisa langsung diketahui tanpa perlu menunggu
  • Overhead sinkronisasi lebih besar daripada manfaatnya

Contoh Implementasi (Golang) #

Studi Kasus: Producer–Consumer Sederhana #

Kita akan membuat queue sederhana di mana consumer akan menunggu jika data belum tersedia.

Implementasi dengan sync.Cond #

package main

import (
    "fmt"
    "sync"
)

type Queue struct {
    items []int
    lock  sync.Mutex
    cond  *sync.Cond
}

func NewQueue() *Queue {
    q := &Queue{}
    q.cond = sync.NewCond(&q.lock)
    return q
}

func (q *Queue) Enqueue(item int) {
    q.lock.Lock()
    defer q.lock.Unlock()

    q.items = append(q.items, item)
    q.cond.Signal() // beri tahu goroutine yang menunggu
}

func (q *Queue) Dequeue() int {
    q.lock.Lock()
    defer q.lock.Unlock()

    for len(q.items) == 0 {
        q.cond.Wait() // guarded suspension
    }

    item := q.items[0]
    q.items = q.items[1:]
    return item
}

func main() {
    queue := NewQueue()

    go func() {
        for i := 1; i <= 5; i++ {
            queue.Enqueue(i)
            fmt.Println("Produced:", i)
        }
    }()

    go func() {
        for i := 1; i <= 5; i++ {
            item := queue.Dequeue()
            fmt.Println("Consumed:", item)
        }
    }()

    select {}
}

Penjelasan #

  • len(q.items) == 0 adalah guard condition

  • cond.Wait() akan:

    • Melepas mutex
    • Men-suspend goroutine
    • Mengunci kembali mutex saat dibangunkan
  • cond.Signal() membangunkan goroutine yang sedang menunggu

Inilah implementasi klasik Guarded Suspension Pattern.


Alternatif Implementasi dengan Channel #

Go juga memungkinkan implementasi Guarded Suspension Pattern secara lebih idiomatis menggunakan channel.

func main() {
    ch := make(chan int)

    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i // producer
        }
        close(ch)
    }()

    for item := range ch {
        fmt.Println("Consumed:", item)
    }
}

Channel secara implisit sudah menerapkan Guarded Suspension:

  • Receive akan menunggu jika channel kosong
  • Send akan menunggu jika channel penuh (buffered channel)

Kesalahan Umum #

  • Menggunakan busy waiting (for { if condition { ... } })
  • Lupa memanggil Signal() / Broadcast()
  • Menggunakan if alih-alih for sebelum Wait()
  • Over-engineering untuk kasus sederhana

Best Practices #

  1. Gunakan for, bukan if, saat Wait() Guard condition harus dicek ulang karena kemungkinan spurious wakeup.

  2. Jaga critical section tetap kecil Jangan lakukan operasi berat di dalam mutex lock.

  3. Pilih primitive yang tepat

    • Channel → lebih idiomatis di Go
    • sync.Cond → lebih fleksibel untuk kondisi kompleks
  4. Hindari deadlock Pastikan selalu ada path yang memanggil Signal() atau Broadcast().

  5. Dokumentasikan guard condition Guard condition adalah kontrak logika antar goroutine.


Penutup #

Guarded Suspension Pattern adalah salah satu pattern fundamental dalam concurrency yang membantu kita membangun sistem yang aman, efisien, dan terprediksi. Pattern ini sangat berguna ketika eksekusi harus menunggu kondisi tertentu tanpa membuang resource.

Di Golang, pattern ini bisa diimplementasikan baik secara eksplisit menggunakan sync.Cond, maupun secara implisit dan lebih idiomatis menggunakan channel. Memahami pattern ini akan sangat membantu saat membangun worker pool, queue system, event-driven architecture, dan sistem concurrent lainnya.

Jika kamu sering berurusan dengan concurrency, Guarded Suspension Pattern adalah senjata wajib di toolbox kamu.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact