Menulis kode program yang berjalan secara asinkron (asynchronous) dan paralel (parallel) adalah salah satu tantangan terbesar bagi pengembang perangkat lunak. Tanpa pemahaman arsitektur yang kuat, kode konkurensi (concurrent code) rentan mengalami masalah klasik seperti race conditions, deadlocks, resource leaks, dan penurunan performa akibat thread contention.
Untuk menyelesaikannya secara benar, industri telah merumuskan Concurrency Patterns yaitu kumpulan solusi arsitektural teruji untuk memecahkan masalah konkurensi spesifik.
Dalam artikel ini, kita akan membedah 39 Concurrency Patterns yang dikelompokkan berdasarkan masalah nyata yang diselesaikannya.
Peta Arsitektur Konkurensi
Sebelum masuk ke detail setiap pola, mari kita lihat bagaimana pola-pola ini berinteraksi dalam ekosistem sistem modern:

Kategori Thread & Task Management (8 Pattern)
Kategori ini berfokus pada cara mengalokasikan unit pemrosesan fisik/virtual dan menjadwalkan eksekusi tugas (tasks).
- Thread Pool: Menggunakan kembali sejumlah thread yang sudah dialokasikan sebelumnya secara tetap daripada membuat dan menghancurkan thread berulang kali.
- Worker Pool: Variasi dari Thread Pool di mana sejumlah worker goroutines/threads secara aktif menarik dan mengeksekusi tugas dari sebuah antrean bersama (shared queue).
- Fork/Join: Memecah sebuah tugas besar menjadi beberapa sub-tugas secara paralel (fork), lalu menggabungkan kembali hasilnya setelah semua sub-tugas selesai (join).
- Work Stealing: Algoritma penjadwalan di mana thread yang sedang menganggur (idle) secara aktif "mencuri" tugas dari antrean thread lain yang sedang sibuk untuk menyeimbangkan beban kerja.
- Fiber / Green Thread: Thread sangat ringan (lightweight) yang dijadwalkan secara virtual oleh runtime aplikasi (seperti Goroutine di Go atau Virtual Thread di Java), bukan langsung dikelola oleh kernel sistem operasi.
- Coroutine: Subrutin yang eksekusinya dapat ditangguhkan (suspend) dan dilanjutkan kembali (resume) di titik tertentu tanpa memblokir thread fisik.
- Task Parallelism: Pembagian kerja di mana berbagai tugas independen yang berbeda dijalankan secara paralel secara eksplisit pada data yang sama atau berbeda.
- Pipeline Pattern: Membagi tugas pemrosesan menjadi serangkaian tahapan (stages) independen, di mana output dari satu tahap langsung menjadi input untuk tahap berikutnya.
Kategori Synchronization & Mutual Exclusion (7 Pattern)
Kategori ini menangani koordinasi akses data bersama (shared state) agar tidak terjadi tabrakan data (data race).
Berikut adalah perbedaan mendasar dalam sistem berpikir antara pendekatan berbagi data terproteksi (shared state) dengan pendekatan pengiriman pesan (message passing):

- Monitor Object: Pola yang menyinkronkan semua metode di dalam suatu objek sehingga hanya ada satu thread yang dapat mengeksekusi metode objek tersebut pada satu waktu (seperti blok
synchronizedpada Java). - Mutex / Lock: Mekanisme penguncian biner (binary semaphore) yang memastikan hanya satu thread yang dapat masuk ke dalam wilayah kritis (critical section).
- Read-Write Lock: Penguncian yang mengizinkan banyak pembaca (multiple readers) mengakses data secara bersamaan, namun membatasi penulis (writer) secara eksklusif.
- Semaphore: Variabel penghitung (counter) yang digunakan untuk mengontrol akses ke sejumlah $N$ sumber daya secara bersamaan.
- Condition Variable: Variabel yang membuat suatu thread menunggu (wait) hingga kondisi logis tertentu terpenuhi sebelum melanjutkan eksekusi.
- Spin Lock: Teknik penguncian di mana thread melakukan putaran berulang (busy-wait loop) untuk memeriksa ketersediaan kunci, cocok untuk kunci yang ditahan dalam waktu sangat singkat guna menghindari overhead context switch.
- Barrier: Titik sinkronisasi di mana semua thread yang berpartisipasi harus berhenti dan menunggu hingga semua thread lain mencapai titik tersebut sebelum diperbolehkan lanjut bersama-sama.
Kategori Message Passing & Communication (5 Pattern)
Pola komunikasi yang menghindari penggunaan memori bersama (shared memory) dengan cara berkirim pesan.
- Actor Model: Unit komputasi independen (Actor) yang mempertahankan status internalnya sendiri (no shared state) dan berkomunikasi secara eksklusif melalui pengiriman pesan asinkron.
- CSP (Channel-based): Pendekatan konkurensi (seperti Go goroutine) di mana proses berkomunikasi secara sinkron (rendezvous) melalui saluran bertipe (typed channels).
- Mailbox / Message Queue: Kotak masuk pesan yang dimiliki secara eksklusif oleh suatu Actor atau thread untuk memproses pesan masuk satu per satu secara berurutan (FIFO).
- Blackboard: Basis pengetahuan bersama (shared knowledge base) di mana beberapa agen independen secara aktif membaca dan menulis data untuk memecahkan masalah kompleks secara kolaboratif.
- Tuple Space: Memori asosiatif bersama (shared associative memory) di mana beberapa proses dapat menaruh (deposit), membaca, atau mengambil kembali objek terstruktur (tuples).
Kategori Async & Event-Driven (6 Pattern)
Pola untuk mengelola operasi I/O non-blocking dan penanganan kejadian (event handling).
- Reactor: Loop kejadian tunggal (single-threaded event loop) yang mendeteksi kejadian I/O masuk dan langsung mendistribusikannya (dispatch) ke fungsi penangan (handlers) yang sesuai secara sinkron.
- Proactor: Pendekatan penanganan asinkron berbasis penyelesaian (completion-based), di mana operasi I/O dilakukan secara asinkron oleh kernel OS, dan penangan dipanggil setelah operasi tersebut selesai sepenuhnya.
- Future / Promise: Objek penampung (placeholder) untuk nilai yang belum selesai dihitung pada saat ini, namun akan tersedia di masa mendatang.
- Async/Await: Konstruksi sintaksis (syntactic sugar) di atas konsep Future/Promise yang memungkinkan penulisan kode asinkron dengan gaya penulisan sinkron yang mudah dibaca.
- Event-Driven Architecture: Arsitektur di mana komponen-komponen terpisah berkomunikasi dengan cara mempublikasikan (publish) dan berlangganan (subscribe) kejadian (events).
- Callback: Fungsi yang diteruskan sebagai argumen ke fungsi lain dengan kesepakatan akan dipanggil kembali ketika suatu tugas asinkron selesai dilakukan.
Kategori Request Management & Rate Control (6 Pattern)
Kategori ini mengontrol aliran dan volume request agar sistem tidak kewalahan (overload).
- Singleflight: Pola deduplikasi yang memastikan bahwa jika ada beberapa request identik yang masuk secara bersamaan, hanya satu request yang benar-benar dieksekusi ke server/database, dan hasilnya akan dibagikan ke semua penanya.
- Rate Limiter: Membatasi jumlah operasi atau request yang dapat dilakukan per satuan waktu (misalnya menggunakan algoritma token bucket atau leaky bucket).
- Debounce: Menunda eksekusi suatu fungsi hingga aktivitas pemicunya berhenti selama interval waktu tertentu (sangat berguna untuk fitur pencarian otomatis).
- Throttle: Membatasi eksekusi suatu fungsi maksimal sekali dalam setiap interval waktu tertentu, terlepas dari seberapa sering fungsi tersebut dipicu.
- Backpressure: Mekanisme di mana penerima data (consumer) mengirimkan sinyal umpan balik ke pengirim data (producer) untuk memperlambat laju pengiriman jika penerima mulai kewalahan.
- Load Shedding: Tindakan menolak request baru secara aktif dan cepat ketika mendeteksi bahwa sistem sudah berada dalam kondisi kelebihan beban (overloaded) guna menjaga kestabilan layanan yang sedang berjalan.
Kategori Lifecycle & Object Reuse (3 Pattern)
Kategori ini berfokus pada efisiensi alokasi memori dan siklus hidup resource konkurensi.
- Object Pool: Menyimpan kumpulan objek mahal yang siap pakai (seperti koneksi database) untuk digunakan kembali daripada membuat objek baru setiap saat.
- Resource Acquisition Is Initialization (RAII): Konsep di mana kepemilikan suatu resource diikat pada siklus hidup objek; resource secara otomatis dilepaskan ketika objek tersebut keluar dari cakupan akses (out of scope).
- Half-Sync/Half-Async: Memisahkan sistem menjadi dua lapisan: lapisan asinkron (untuk menangani operasi I/O cepat) dan lapisan sinkron (untuk memproses logika bisnis yang lebih lambat), dihubungkan oleh sebuah antrean pesan (queue).
Kategori Data Safety & Immutability (4 Pattern)
Kategori yang memastikan data tidak rusak saat diakses secara konkuren tanpa menggunakan kunci (lock-free).
- Immutable Object: Objek yang isinya tidak dapat diubah setelah dibuat, sehingga secara alami aman untuk dibagikan ke banyak thread tanpa sinkronisasi kunci.
- Thread-Local Storage: Mekanisme yang menyediakan salinan variabel terpisah bagi setiap thread, memastikan tidak ada pembagian memori antar thread untuk variabel tersebut.
- Copy-on-Write (COW): Membagi data secara bersamaan hingga ada salah satu pihak yang ingin memodifikasinya; pada saat itulah salinan data baru dibuat secara terpisah.
- Software Transactional Memory (STM): Mengelola perubahan memori mirip seperti transaksi database; perubahan dilakukan secara optimistik dan akan di-commit jika tidak terjadi konflik dengan transaksi lain.
Implementasi Konkurensi
Sebagai contoh mari kita lihat diagram alur logika dari dua pola konkurensi krusial: Go Channel Pipeline dan Singleflight Deduplication:

1. Implementasi Pipeline Pattern di Go
Pola ini sangat efisien untuk memproses aliran data besar secara bertahap tanpa membebani memori:
package main
import "fmt"
// Generator: Tahap awal membuat data dan mengirimkannya ke channel
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// Squarer: Tahap transformasi (mengalikan angka dengan dirinya sendiri)
func squarer(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Setup Pipeline: generator -> squarer
inputChan := generator(2, 3, 4, 5)
resultChan := squarer(inputChan)
// Consumer: Mengonsumsi hasil akhir
for result := range resultChan {
fmt.Println(result) // Output: 4, 9, 16, 25
}
}
2. Implementasi Singleflight di Go
Pola ini sangat berguna untuk memproteksi database dari cache stampede (lonjakan request tiba-tiba untuk data yang sama):
package main
import (
"fmt"
"sync"
"time"
)
// call merepresentasikan request yang sedang berjalan
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
// Group mengelola request duplikat
type Group struct {
mu m sync.Mutex
m map[string]*call
}
// Do mengeksekusi fungsi fn, memastikan hanya ada satu eksekusi untuk key yang sama
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait() // Tunggu hasil dari request pertama yang sedang berjalan
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn()
c.wg.Done() // Sinyalkan bahwa eksekusi selesai kepada semua penunggu
g.mu.Lock()
delete(g.m, key) // Hapus key dari map agar request berikutnya bisa berjalan baru
g.mu.Unlock()
return c.val, c.err
}
Memantau Kinerja Sistem Konkuren
Aplikasi dengan konkurensi tinggi membutuhkan antarmuka pemantauan (monitoring dashboard) untuk memantau status antrean worker dan efisiensi kunci secara real-time:

Referensi & Resource Penting
- Go Concurrency Patterns. Video presentasi legendaris oleh Rob Pike mengenai pemanfaatan channel dan goroutine: Google I/O 2012 Talk.
- Schmidt, D. C., Stal, M., Rohnert, H., & Buschmann, F. (2000). Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects. Wiley. Buku akademis rujukan utama untuk pola Reactor, Proactor, dan Half-Sync/Half-Async.
- Go singleflight package. Dokumentasi resmi paket pustaka sinkronisasi singleflight: Go singleflight package.
- Larus, J. R., & Rajwar, R. (2007). Transactional Memory. Morgan & Claypool Publishers. Buku komprehensif mengenai konsep dasar Software Transactional Memory (STM).