Mutex & sync package — Go

Mutex & Package sync Goroutine mudah dibuat, tapi ketika banyak goroutine menyentuh data yang sama, muncul bahaya: race condition. Package sync menyediakan…

Mutex (mutual exclusion) adalah kunci yang memastikan cuma satu goroutine yang menyentuh data bersama pada satu waktu. Bayangin buku catatan penjualan di meja kasir Warung Bakso Mas Bimo, dengan dua pelayan yang mencatat. Kalau dua-duanya membaca "41 porsi" di saat yang sama, lalu masing-masing menulis "42", satu penjualan hilang dari catatan. Solusinya: cuma ada satu pulpen. Siapa yang memegang pulpen boleh membaca dan menulis, yang lain menunggu sampai pulpennya dikembalikan. Pulpen itu mutex.

🧪 Tebakan cepat: 100 goroutine masing-masing memanggil catat() 10 kali, dan catat() cuma berisi terjual++. Apakah hasilnya selalu terjual: 1000? Tebak dulu, jawabannya ada di bagian "Race condition: penjualan yang hilang".

Race condition: penjualan yang hilang

package main

import (
	"fmt"
	"sync"
)

var terjual int

func catat() {
	terjual++
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 10; j++ {
				catat()
			}
		}()
	}
	wg.Wait()
	fmt.Println("terjual:", terjual)
}

Program ini dijalankan 18 kali di Go Playground (8 CPU). 14 kali hasilnya terjual: 1000, tapi empat kali hasilnya terjual: 950, terjual: 990, terjual: 990, dan terjual: 970. Jadi gak selalu 1000. Itu jawaban tebakannya.

Kenapa? terjual++ kelihatan satu langkah, padahal tiga: baca nilainya, tambah satu, tulis lagi. Dua goroutine bisa saling salip di tengahnya:

Langkah Goroutine A Goroutine B terjual
1 baca: 41 41
2 baca: 41 41
3 tulis 42 42
4 tulis 42 42

Dua penjualan, tapi hitungannya cuma naik satu. Ini disebut race condition: dua goroutine atau lebih menyentuh variabel yang sama, minimal satu menulis, tanpa sinkronisasi. Bug-nya cuma muncul kalau jadwalnya pas saling salip, jadi kadang muncul, kadang gak. Itu yang bikin race susah dicari.

sync.Mutex: satu goroutine satu waktu

var (
	terjual int
	mu      sync.Mutex
)

func catat() {
	mu.Lock()
	defer mu.Unlock()
	terjual++
}

Sekarang B yang datang saat A memegang kunci harus menunggu, lalu membaca 42, bukan 41. Hasilnya selalu terjual: 1000.

Di latihan: counter yang aman

Latihan lesson ini memakai pola yang sama dengan nama lain: variabel counter, mutex mu, dan fungsi increment(). main sudah menjalankan 100 goroutine yang masing-masing memanggil increment() 10 kali, lalu mencetak Counter: 1000. Tugasmu mengisi increment() dengan mu.Lock(), defer mu.Unlock(), dan counter++.

sync.RWMutex: banyak pembaca, satu penulis

Kalau datanya sering dibaca tapi jarang diubah, misalnya daftar harga, sync.RWMutex lebih cocok:

var (
	harga = map[string]int{"bakso urat": 15000, "mie ayam": 12000, "es teh": 5000}
	hmu   sync.RWMutex
)

func lihatHarga(menu string) int {
	hmu.RLock()
	defer hmu.RUnlock()
	return harga[menu]
}

func ubahHarga(menu string, baru int) {
	hmu.Lock()
	defer hmu.Unlock()
	harga[menu] = baru
}

RLock boleh dipegang banyak pembaca sekaligus. Lock tetap eksklusif: penulis menunggu semua pembaca selesai, dan pembaca baru menunggu penulisnya. Kalau tulis sama seringnya dengan baca, sync.Mutex biasa lebih sederhana dan sering lebih cepat.

💡 Mendeteksi race dengan -race

Go punya race detector: go run -race bakso.go. Kalau ada race, program mencetak laporan yang diawali WARNING: DATA RACE, menyebut baca dan tulis yang bertabrakan beserta goroutine dan baris kodenya, lalu keluar dengan exit status 66. Jalankan test dengan go test -race juga. Map lebih galak lagi: menulis map dari banyak goroutine tanpa kunci bisa langsung mematikan program dengan fatal error: concurrent map writes.

⚠️ Jebakan yang sering ketemu

TL;DR: Race condition terjadi kalau dua goroutine atau lebih menyentuh data yang sama, minimal satu menulis, tanpa sinkronisasi; terjual++ itu baca, tambah, tulis, jadi update bisa hilang (di Go Playground: 950 dari 1000). sync.Mutex membuat cuma satu goroutine di critical section: mu.Lock(), defer mu.Unlock(), lalu ubah datanya. Pakai sync.RWMutex (RLock untuk baca, Lock untuk tulis) kalau datanya banyak dibaca dan jarang ditulis. Cari race dengan go run -race. Jangan Lock dua kali, jangan Unlock tanpa Lock, dan jangan menyalin mutex.

Yang akan kamu pelajari