Published

4 min read

Jaring Pengaman: Sinkronisasi dengan `sync` & Race Detector


Selamat datang di babak final dari seri konkurensi Becoming Gopher! Sejauh ini, kita telah mengikuti filosofi Go: berkomunikasi antar goroutine dengan mengirim pesan melalui channel. Ini adalah cara yang elegan dan aman.

Tapi, terkadang ada situasi di mana berbagi memori secara langsung tidak bisa dihindari, terutama untuk alasan performa atau saat berinteraksi dengan library eksternal. Untuk kasus-kasus ini, Go menyediakan ‘jaring pengaman’ di dalam paket sync.

Di episode terakhir ini, kita akan membahas:

  1. sync.Mutex: Cara mengunci data agar hanya bisa diakses oleh satu goroutine pada satu waktu.
  2. sync.WaitGroup: Cara elegan untuk menunggu sekelompok goroutine menyelesaikan tugasnya.
  3. Race Detector: Alat pamungkas dari Go untuk mendeteksi bug konkurensi secara otomatis.

sync.Mutex: Satu Ruangan, Satu Kunci

Ingat masalah race condition pada counter kita di postingan pertama? Banyak goroutine mencoba mengubah variabel yang sama secara bersamaan, menyebabkan kekacauan.

Paket sync menyediakan solusi klasik untuk ini: Mutex (Mutual Exclusion).

Bayangkan variabel counter kita berada di dalam sebuah ruangan. Mutex adalah satu-satunya kunci untuk masuk ke ruangan itu. Sebelum sebuah goroutine bisa mengubah counter, ia harus mengambil kunci (Lock). Setelah selesai, ia harus mengembalikan kunci (Unlock) agar goroutine lain bisa masuk. Yang lain harus antre dengan tertib.

Mari kita perbaiki kode counter kita menggunakan Mutex.

package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex // Membuat 'kunci'
counter := 0
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock() // Ambil kunci sebelum mengakses counter
counter++
mu.Unlock() // Kembalikan kunci setelah selesai
}()
}
wg.Wait()
fmt.Println("Nilai akhir counter:", counter) // Output: 1000
}

Dengan Mutex, hasilnya sekarang selalu konsisten: 1000.

sync.WaitGroup: Menunggu Rombongan Sampai Selesai

Di postingan pertama, kita juga punya masalah di mana main goroutine selesai duluan. Solusi sementara kita adalah time.Sleep(), yang sangat buruk. sync.WaitGroup adalah solusi yang benar untuk masalah ini.

Bayangkan WaitGroup sebagai seorang mandor proyek yang tahu persis berapa banyak pekerja yang harus ia tunggu.

Ia memiliki tiga operasi utama:

  1. Add(n): Mandor berkata, “Saya menunggu n pekerja.”
  2. Done(): Setiap pekerja, setelah selesai, melapor, “Tugas saya selesai!”. Ini akan mengurangi hitungan mandor sebanyak satu.
  3. Wait(): Mandor akan berhenti dan menunggu di sini sampai hitungannya menjadi nol.
func worker(id int, wg *sync.WaitGroup) {
// Pastikan worker melapor 'Done' saat fungsinya berakhir
defer wg.Done()
fmt.Printf("Worker %d mulai bekerja.\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d selesai.\n", id)
}
func main() {
var wg sync.WaitGroup
// 1. Beri tahu WaitGroup kita akan menunggu 3 worker
wg.Add(3)
for i := 1; i <= 3; i++ {
go worker(i, &wg)
}
// 3. Tunggu sampai semua worker memanggil Done()
wg.Wait()
fmt.Println("Semua pekerjaan telah diselesaikan.")
}

Tidak ada lagi tebak-tebakan. Program akan menunggu persis selama yang dibutuhkan.

Alat Pamungkas: Race Detector

Bagaimana jika kita tidak sengaja lupa menggunakan mutex atau channel? Apakah kita harus mencari bug-nya semalaman? Tentu tidak. Tim Go telah memberi kita alat pamungkas: Race Detector.

Ini adalah fitur bawaan yang bisa diaktifkan saat menjalankan program. Ia akan memantau akses memori dan melaporkan jika ada potensi race condition.

Cukup tambahkan flag -race saat menjalankan kodemu.

Terminal window
go run -race namafile.go

Jika kita menjalankan kode counter yang salah (tanpa Mutex) dengan race detector, kita akan mendapatkan laporan yang sangat jelas seperti ini:

==================
WARNING: DATA RACE
Write at 0x0000012e62d8 by goroutine 8:
main.main.func1()
/path/to/project/main.go:18 +0x3c
Previous read at 0x0000012e62d8 by goroutine 7:
main.main.func1()
/path/to/project/main.go:18 +0x28
...
Found 1 data race(s)
exit status 66

Laporan ini memberitahu kita:

  • Ada data race.
  • Operasi tulis oleh goroutine 8 di main.go baris 18.
  • Bertabrakan dengan operasi baca oleh goroutine 7 di main.go baris 18.

Membiasakan diri menggunakan -race saat pengembangan adalah jaring pengaman terbaik saat menulis kode konkuren.

Akhir dari Petualangan Konkurensi

Selamat! Anda telah menyelesaikan salah satu topik paling menantang dan paling bermanfaat di Go. Anda sekarang memiliki fondasi yang kuat untuk membangun aplikasi yang cepat, efisien, dan tangguh.

Dalam seri ini, kita telah melakukan perjalanan dari:

  • Melepaskan kekuatan goroutine.
  • Berkomunikasi dengan aman melalui channel.
  • Mengorkestrasi banyak channel dengan select.
  • Menggunakan sync sebagai jaring pengaman dan mendeteksi masalah dengan Race Detector.

Ingatlah selalu filosofi Go: “Jangan berkomunikasi dengan berbagi memori; sebaliknya, berbagilah memori dengan berkomunikasi.” Utamakan channels jika memungkinkan, dan gunakan sync saat diperlukan.

Terima kasih telah mengikuti seri konkurensi ini. Selamat ngoding, Gopher sejati!