sync.WaitGroup & sync.Mutex
Tunggu sekumpulan goroutine dan lindungi data bersama.
Pahami konsepnya
sync.WaitGroup adalah alat hitung-mundur untuk menunggu banyak goroutine: Add(n) menambah penghitung sebelum meluncurkan, Done() di tiap goroutine menurunkannya (biasanya lewat defer), dan Wait() memblokir sampai nol. Inilah pengganti time.Sleep yang benar saat channel tidak diperlukan.
Ketika banyak goroutine MENULIS variabel yang sama, balapan data terjadi: dua penambahan berbarengan saling menimpa, hasil akhir lebih kecil dari seharusnya. Deteksinya mudah: go test -race atau go run -race menampilkan laporan race detector. Solusinya sync.Mutex — kunci sebelum menyentuh data, buka setelahnya (defer mu.Unlock()).
Channel dan mutex menyelesaikan masalah berbeda: channel memindahkan kepemilikan data antar goroutine (berkomunikasi), mutex melindungi akses ke satu memori bersama. Prinsip Rob Pike yang terkenal: "jangan berkomunikasi dengan berbagi memori; bagikan memori dengan berkomunikasi" — tapi keduanya sama-sama sah dan saling melengkapi.
Coba contoh kode
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
total := 0
var mu sync.Mutex
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
total++ // bagian kritis: satu-satunya penulis diizinkan
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(total) // selalu 1000
}Tanpa mutex, hasil bisa 972 atau 989 — dua goroutine membaca nilai sama lalu menulis hasil sama, satu penambahan lenyap. Lock/Unlock membuat total++ atomik: satu penghitung masuk, lainnya antre. wg.Wait memastikan pencetakan menunggu seluruh 1000 goroutine tuntas. Hilangkan mu dan jalankan go run -race untuk melihat laporan balapannya.
WaitGroup tanpa data bersama
func prosesParalel(tugas []string) {
var wg sync.WaitGroup
for _, t := range tugas {
wg.Add(1)
go func(nama string) {
defer wg.Done()
fmt.Println("selesai:", nama)
}(t)
}
wg.Wait()
fmt.Println("semua tugas tuntas")
}Bila goroutine tidak berbagi variabel, mutex tidak diperlukan — WaitGroup saja cukup sebagai pembatas akhir. Add dipanggil SEBELUM go (bukan di dalam goroutine) agar penghitung tidak pernah sempat nol lebih awal — jebakan umum yang membuat Wait lolos terlalu cepat.
Latihan singkat
Uji: seribu goroutine menambah counter — dengan mutex program harus mencetak persis `1000`. Lalu hapus mutex dan lihat hasilnya goyang.