Salah satu kelebihan golang adalah dapat digunakan untuk membuat program yang concurrent dengan mudah, tapi perlu diketahui juga bahwa setiap program concurrency terdapat kemungkinan adanya data race.

Data Race

Definisi data race spesifik pada model concurrency umumnya mengacu pada situasi dimana sebuah lokasi memori diproses oleh dua thread atau lebih, secara bersamaan ada thread yang melakukan proses menulis/mengubah nilai memori tersebut. Hal tersebut memungkinkan data yang di proses oleh thread lain sudah tidak sesuai dengan data yang disimpan pada memori tersebut, sehingga beberapa thread dapat menghasilkan nilai yang berbeda, bahkan tidak berguna lagi karena tidak dapat mewakili nilai dari memori yang ada sesudah diubah1.

Di golang hal ini dapat terjadi ketika ada dua atau lebih goroutine mengakses variabel yang sama secara concurrent, dan salah satu akses goroutine adalah menulis ke variabel tersebut2. Hal ini dapat menjadi bug pada program karena berpotensi adanya perilaku yang tidak diinginkan pada goroutine lain.

Contoh Program dengan Data Race

Seperti yang kita tahu menulis program menggunakan goroutine akan membagi komponen dari program tersebut menjadi elemen yang dependen seperti pada contoh kode2 berikut:

package main

import "fmt"

func main(){

   i := 0

  go func(){ // goroutine
     i=100
   }()

  fmt.Println(i)
 }
Code Snippet 1: Contoh program dengan data race

Dari contoh kode diatas jika di jalankan ada kemungkinan hasil outputnya adalah 100, tapi tidak menjamin setiap program dieksekusi hasilnya tepat seperti yang diinginkan. Disini fungsi goroutine go func() mengubah nilai i dan di fungsi main() menampilkan nilai i, kedua fungsi berjalan secara dependen dan tidak akan menunggu fungsi satu sama lain.

Table 1: Salah satu alur yang bisa terjadi
main() go func() nilai i
inisialisasi i 0
menjalankan go func() 0
membaca i 0
membaca i 0
menampilkan i 0
menulis i 100

Dalam balapan seperti ini (atau race condition1), jelas fungsi main akan selesai terlebih dahulu karena fungsi ini yang pertama memulai dan menjalankan fungsi goroutine go func(). Dan ketika fungsi goroutine selesai mengubah nilai i, nilai i sudah ditampilkan terlebih dahulu oleh fungsi main(). Inilah yang menyebabkan nilai yang di tampilkan fungsi main adalah 0.

Mendeteksi Data Race

Data race sangatlah umum terjadi dan sangat sulit di debug pada program yang melakukan proses data yang kompleks. Untuk mendiagnosis bug seperti ini, golang memiliki alat untuk mendeteksi data race3. Untuk menggunakannya, tambahkan flag -race pada perintah untuk menjalankan program:

Table 2: Penggunaan flag -race
Perintah Keterangan
go test -race namaPackage pada perintah test
go run -race namaFile.go ketika menjalankan source file
go build -race namaPackage ketika melakukan build
go install -race namaPackage ketika menginstall package

Ketika race detector menemukan race pada program, maka akan menampilkan laporan berisi stack trace untuk akses yang konflik beserta dimana goroutine dibuat. Berikut adalah hasil deteksi race pada Kode Snippet 1 sebelumnya.

0
==================
WARNING: DATA RACE
Write at 0x00c00013c008 by goroutine 7:
  main.main.func1()
      /tmp/main.go:10 +0x30

Previous read at 0x00c00013c008 by main goroutine:
  main.main()
      /tmp/main.go:13 +0xbe

Goroutine 7 (running) created at:
  main.main()
      /tmp/main.go:9 +0xae
==================
Found 1 data race(s)
exit status 66

Dari hasil laporan diatas, informasi terbagi menjadi 3 dari atas ke bawah:

  • Pertama menampilkan bahwa adanya aksi menulis dalam goroutine (baris 10 pada kode)
  • Kedua aksi baca pada main() goroutine dimana fungsi fmt.Println() digunakan (baris 13 pada kode)
  • Terakhir baris dimana goroutine dibuat (baris 9 pada kode)

Menghindari Data Race

Satu-satunya cara untuk menghindari data race adalah menyinkronkan akses pada semua data mutable yang di bagikan aksesnya antar goroutine. Adapun untuk melakukan hal tersebut menggunakan channel atau menggunakan mekanisme low level lain yang yang tersedia pada package sync4 seperti WaitGroup dan Mutex.

Don’t communicate by sharing memory;
share memory by communicating2.

package main

import "fmt"

func main() {

  c := make(chan bool)
  i := 0
  go func() { // goroutine
    i = 100
    c <- true
    close(c)
  }()
  <-c // tunggu
  fmt.Println(i)
}
Code Snippet 2: Menunggu goroutine menggunakan channel

Pada contoh diatas adalah salah satu contoh untuk mengatasi data race pada kode sebelumnya menggunakan channel. Coba jalankan menggunakan -race flag untuk memastikan tidak ada data race.

Referensi


  1. Race condition <2022-06-10 Jum> ↩︎

  2. Data races explained <2022-06-10 Jum> ↩︎

  3. Data Race Detector <2022-06-10 Jum> ↩︎

  4. package sync - sync - Go Packages <2022-06-10 Jum> ↩︎