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)
}
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.
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:
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 fungsifmt.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 sync
4 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)
}
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.