Episode ini mempelajari concurrency sebagai identitas Go: goroutine, channel, dan select, lalu context.Context untuk cancellation dan deadline, serta sinkronisasi dengan sync.Mutex, WaitGroup, Once, dan atomic operations.

Inilah identitas paling khas dari Go: concurrency. Di episode 1 kalian sudah melihat goroutine sekilas. Sekarang kita dalami cara mengelola banyak tugas yang berjalan bersamaan secara aman, tanpa race condition dan kebocoran goroutine.
Episode 12 membahas tiga lapisan: goroutine, channel, dan select sebagai primitive concurrency; context.Context untuk cancellation, deadline, dan nilai ber-skope-request; serta primitif sinkronisasi sync.Mutex, WaitGroup, Once, dan atomic operations.
Goroutine adalah fungsi yang berjalan konkuren, dimulai dengan kata kunci go. Jadwal eksekusinya dikelola runtime, dan biayanya hanya beberapa kilobyte stack yang tumbuh dinamis. Deteksi race condition pada kode ini bisa dilakukan dengan go test -race.
package main
import (
"fmt"
"time"
)
func kerja(nama string) {
for i := 1; i <= 3; i++ {
fmt.Println(nama, i)
time.Sleep(10 * time.Millisecond)
}
}
func main() {
go kerja("pertama")
go kerja("kedua")
time.Sleep(100 * time.Millisecond)
}Perhatikan time.Sleep di main untuk menunggu goroutine. Ini contoh buruk di production — cara yang benar memakai WaitGroup yang akan kita bahas sebentar lagi.
Channel adalah pipa komunikasi antar goroutine. Kirim dengan ch <- nilai, terima dengan nilai := <-ch. Channel membawa data dan sekaligus menyinkronkan: pengiriman menunggu sampai ada penerima.
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "hasil perhitungan"
}()
pesan := <-ch
fmt.Println(pesan)
}Channel ber-buffer seperti make(chan int, 10) mengizinkan beberapa nilai antre tanpa menunggu penerima. Channel yang tidak ditutup dan tidak pernah dipakai bisa menyebabkan deadlock — biasakan menutup channel di sisi pengirim dengan close.
select menunggu operasi pada banyak channel sekaligus dan menjalankan yang siap. Ini fondasi untuk timeout, cancellation, dan fan-in:
select {
case hasil := <-hasilChan:
fmt.Println("hasil:", hasil)
case <-time.After(1 * time.Second):
fmt.Println("timeout, batalkan")
}context.Context membawa batas waktu dan pembatalan ke seluruh call stack. Ketika request dibatalkan atau deadline terlampaui, semua goroutine yang menerima context berhenti bekerja sama.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-time.After(3 * time.Second):
fmt.Println("selesai tanpa masalah")
case <-ctx.Done():
fmt.Println("dibatalkan:", ctx.Err())
}
}ctx.Done() menutup channel ketika context dibatalkan. context.WithCancel dan context.WithValue adalah dua variasi lain yang sering dipakai di server HTTP. Nilai yang dibawa WithValue digunakan untuk metadata seperti request ID, bukan data bisnis.
WaitGroup menunggu sekumpulan goroutine selesai. Add menambah penghitung, Done menguranginya, dan Wait memblokir sampai penghitung nol.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("proses", n)
}(i)
}
wg.Wait()
fmt.Println("semua selesai")
}Jangan memanggil Add setelah Wait dimulai. defer wg.Done() memastikan penghitung selalu berkurang meskipun goroutine panic.
Ketika banyak goroutine menulis ke data yang sama, race condition mengintai. sync.Mutex mengunci akses agar hanya satu goroutine pada satu waktu.
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Tambah() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}sync.Once menjalankan fungsi persis satu kali, ideal untuk inisialisasi singleton yang dipanggil dari banyak goroutine. Sementara itu, operasi atomik di sync/atomic memungkinkan pembaruan angka tanpa mutex, sangat cepat untuk counter sederhana:
var nilai atomic.Int64
func main() {
nilai.Add(1)
fmt.Println(nilai.Load())
}atomic.Int64 tipe-generik sejak Go 1.19 menyediakan method Add, Load, Store, dan Swap dengan jaminan atomic. Kombinasi primitive ini membentuk pola worker pool yang umum: membatasi goroutine aktif dengan membaca task dari channel bersama, sementara channel hasil menampung output dari semua worker.
Episode 12 membuka kekuatan concurrency Go: goroutine sebagai unit eksekusi ringan, channel sebagai sarana komunikasi, select untuk multiplexing banyak channel, context.Context untuk cancellation dan deadline, serta sync.Mutex, WaitGroup, Once, dan atomic untuk sinkronisasi yang aman.
Inti yang harus dibawa pulang:
go.select memilih channel yang siap dan mendukung timeout.context.Context menyebarkan cancellation dan deadline.WaitGroup untuk menunggu kumpulan goroutine.Mutex dan atomic mencegah race condition pada data bersama.Di episode 13 selanjutnya kita akan membahas performansi dan optimasi Go — profiling dengan pprof, benchmark dengan go test -bench, optimasi memori dan allocation dengan escape analysis, serta teknik performance tuning seperti buffer reuse dan zero allocation patterns. Aplikasi kalian akan berjalan lebih cepat dan lebih hemat resource.