← Kembali ke kursus
Pelajaran kursus · Go
Pelajaran 54

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.