Go Concurrency Goroutines Channels Select Context Cancellation
Definisi
Concurrency di Go adalah cara menyusun program supaya beberapa pekerjaan bisa berjalan tumpang tindih. Go menyediakan beberapa alat utama untuk itu:
goroutine untuk menjalankan fungsi secara concurrent
channel untuk mengirim data atau sinyal antar goroutine
select untuk menunggu beberapa channel sekaligus
worker pattern untuk membatasi dan mengatur pemrosesan paralel
context cancellation untuk menghentikan pekerjaan yang sudah tidak relevan
Concurrency bukan berarti selalu lebih cepat. Tujuan utamanya adalah mengatur alur kerja yang melibatkan banyak tugas, banyak I/O, atau proses yang harus berjalan bersamaan tanpa saling mengunci.
Kapan concurrency dipakai
Concurrency cocok saat pekerjaan bisa dipisah menjadi beberapa unit yang tidak harus menunggu satu sama lain secara ketat.
Cocok untuk concurrency
request ke beberapa service eksternal
baca banyak file atau objek storage
proses pipeline data
streaming event atau message
background task yang bisa berjalan paralel
fan-out / fan-in processing
polling, retry, dan timeout handling
Kurang cocok untuk concurrency
kerja kecil yang selesai sangat cepat
code path yang sederhana dan mudah dibaca secara sinkron
tugas yang butuh urutan ketat tanpa overlap
proses CPU-bound kecil yang overhead concurrency-nya lebih besar dari manfaatnya
Aturan praktisnya:
Pakai concurrency kalau ada I/O waiting, banyak task independen, atau pipeline yang jelas.
Jangan pakai concurrency hanya karena terlihat “lebih advanced”.
Ukur dulu kalau beban kerja kecil; kadang kode sinkron lebih stabil dan lebih mudah dirawat.
Alur dasar goroutine
Goroutine adalah fungsi yang dijalankan secara concurrent oleh runtime Go.
go
go doWork()
Kalau sebuah fungsi dipanggil biasa, eksekusinya berjalan urut di goroutine utama.
Kalau dipanggil dengan go, fungsi itu dijadwalkan untuk jalan sendiri.
Alur sederhananya
Loading diagram...
Yang perlu diingat:
goroutine tidak otomatis menunggu goroutine lain selesai
kalau main selesai lebih dulu, semua goroutine lain ikut berhenti
koordinasi selesai/tidak selesai harus dibuat eksplisit
Contoh di atas menunjukkan bahwa goroutine berjalan paralel secara logis, tetapi main tetap harus memberi waktu atau menunggu dengan cara yang benar. Di aplikasi nyata, jangan mengandalkan Sleep untuk sinkronisasi. Gunakan channel, sync.WaitGroup, atau context.
Channel
Channel adalah jalur komunikasi antar goroutine. Channel dipakai untuk mengirim data, sinyal selesai, atau sinyal pembatalan.
go
ch := make(chan int)
Fungsi utama channel
mengirim data antar goroutine
menyusun alur producer-consumer
memberi sinyal selesai atau error
menghindari koordinasi manual yang rawan race
Cara kerja channel
Urutannya biasanya begini:
satu goroutine mengirim data ke channel
goroutine lain menerima data dari channel
pengirim dan penerima bisa saling menunggu bila channel belum siap
komunikasi selesai saat data berpindah atau channel ditutup
panggil cancel() kalau context dibuat dengan WithCancel, WithTimeout, atau WithDeadline
jangan simpan context di struct untuk dipakai lama-lama
jangan pakai context.Background() di tengah alur kalau sudah punya context dari parent
Jebakan umum
Deadlock
Deadlock terjadi saat goroutine saling menunggu tanpa ada yang bisa lanjut.
Penyebab umum:
kirim ke channel tanpa penerima
menerima dari channel yang tidak akan pernah diisi
select yang semua case-nya block dan tidak ada timeout atau cancel
menunggu WaitGroup yang counter-nya tidak pernah selesai
Contoh pola berbahaya:
go
ch := make(chan int)ch <- 1 // block karena tidak ada penerima
Cara menghindari:
pastikan ada pasangan send/receive
tutup channel di sisi pengirim, bukan sembarangan di banyak tempat
pakai timeout atau context untuk operasi yang bisa macet
Goroutine leak
Goroutine leak terjadi saat goroutine sudah tidak dibutuhkan, tapi tetap hidup karena masih menunggu channel, network, atau sinyal yang tidak datang.
Contoh pemicu:
goroutine menunggu receive selamanya
worker tidak pernah dapat sinyal stop
result channel tidak pernah dibaca sampai habis
context tidak diteruskan ke operasi yang lama
Cara mencegah:
selalu sediakan jalur exit
gunakan ctx.Done() di loop atau blocking select
pastikan semua worker punya kondisi selesai yang jelas
baca semua channel hasil sampai selesai atau batalkan dengan benar
Race condition
Race condition terjadi saat dua goroutine mengakses data yang sama secara bersamaan tanpa proteksi yang benar.
Gejala umum:
hasil tidak konsisten
bug hilang-muncul
test kadang lulus, kadang gagal
panic atau state yang rusak di bawah beban
Cara menghindari:
kirim data lewat channel agar ownership jelas
gunakan sync.Mutex kalau shared state memang perlu
gunakan sync/atomic untuk kasus tertentu yang sederhana
jalankan go test -race saat debugging
Channel yang ditutup salah
Aturan praktis:
sender yang menutup channel
receiver biasanya hanya membaca sampai channel habis
jangan menutup channel yang masih mungkin dipakai sender lain
Menutup channel dua kali akan panic.
Lupa menunggu goroutine selesai
Kalau main selesai duluan, goroutine lain bisa ikut mati sebelum pekerjaan selesai. Ini sering terjadi pada contoh kecil yang terlihat “jalan”, tapi gagal di aplikasi nyata.
Gunakan:
sync.WaitGroup
channel done
context cancellation
Pola yang sehat
1) Fan-out / fan-in
fan-out: satu task dibagi ke beberapa worker
fan-in: hasil dari banyak worker digabung kembali
Pola ini cocok untuk mempercepat pemrosesan paralel yang independen.
2) Pipeline
Task diproses tahap demi tahap lewat beberapa channel.
Contoh umum:
read
transform
validate
write
3) Bounded concurrency
Jangan spawn goroutine tanpa batas kalau input bisa besar. Pakai worker pool atau semaphore untuk menahan jumlah pekerjaan aktif.
4) Cancellation-aware code
Setiap loop atau operasi lama sebaiknya punya jalur berhenti:
select { case <-ctx.Done(): ... }
timeout per langkah
cleanup sebelum return
Ringkasan cepat
goroutine = unit eksekusi concurrent
channel = jalur komunikasi antar goroutine
select = menunggu beberapa channel atau timeout sekaligus
worker pattern = membatasi dan mengatur paralelisme
context cancellation = cara memberi batas waktu dan stop signal
Kalau kamu sedang mendesain flow concurrent di Go, mulai dari pertanyaan ini:
Apa yang benar-benar perlu berjalan bersamaan?
Siapa yang punya data?
Siapa yang menutup alur kerja?
Bagaimana cara berhenti kalau upstream gagal atau timeout?
Bagaimana mencegah deadlock, leak, dan race?
Referensi terkait
sync.WaitGroup untuk menunggu banyak goroutine
sync.Mutex untuk shared state
sync/atomic untuk operasi atomik tertentu
go test -race untuk mendeteksi race condition
context.WithTimeout dan context.WithCancel untuk pembatalan terkontrol