Pada artikel sebelumnya kita telah membahas data race dan cara sederhana untuk menyinkronkan akses data pada setiap goroutine yang mengakses data tersebut menggunakan channel semaphore. Disini kita akan membahas cara lain yang umum ditemukan disetiap bahasa pemrograman yang membahas tentang concurrency yaitu menggunakan Mutex.

Race Condition

Seperti yang telah kita bahas pada artikel sebelumnya data race terjadi ketika terdapat kondisi dimana beberapa goroutine mencoba mengakses dan mengupdate sebuah data dan beberapa goroutine gagal untuk mengupdate data dengan baik dan menghasilkan output yang tidak sesuai dengan yang diharapkan. Kondisi inilah yang disebut sebagai Race Condition.

Mari kita menggunakan contoh berbeda dari artikel sebelumnnya, pada kasus ini kita akan membuat program sederhana dengan menggunakan 1000 goroutine.

package main

import (
  "fmt"
  "sync"
)

func main() {
  var waitGroup sync.WaitGroup
  var nilai int
  for i := 0; i < 1000; i++ {
    waitGroup.Add(1)
    go func(nilai *int, waitGroup *sync.WaitGroup) {
      *nilai++
      waitGroup.Done()
    }(&nilai, &waitGroup)
  }
  waitGroup.Wait()
  fmt.Print("Hasil:", nilai)
}
Code Snippet 1: Race Condition pada Program

Jika kita membaca bagian blok perulangan pada program tersebut sangat jelas bahwa output yang akan dihasilkan adalah 1000 (nilai dari variabel nilai). Tetapi setiap kali program dijalankan selalu menghasilkan outputnya berbeda - beda walaupun tidak jauh dari nilai 1000.

Hasil:988

Cara kerja mutex

Mutual Exclusion (Mutex) artinya setiap goroutine yang akan mengakses dan mengupdate shared data, data tersebut harus di lock, dan setelah data diupdate, data perlu di-unlock agar goroutine lain dapat melakukan hal yang sama.

  • data lock
  • update data
  • data unlock

Dengan begitu, semua goroutine yang mencoba mengakses dan mengupdate data tersebut dapat tersinkron dan terhindar dari data race.

Menggunakan Mutex

Mari kita modifikasi program diatas dengan menambahkan mutex, sehingga dapat terhindar dari data race dan menghasilkan output sesuai dengan yang diharapkan.

package main

import (
  "fmt"
  "sync"
)

func main() {
  var waitGroup sync.WaitGroup
  var mutex sync.Mutex
  var nilai int
  for i := 0; i < 1000; i++ {
    waitGroup.Add(1)
    go func(nilai *int, waitGroup *sync.WaitGroup, mutex *sync.Mutex) {
      // lock
      mutex.Lock()
      // update
      *nilai++
      // unlock
      mutex.Unlock()

      waitGroup.Done()
    }(&nilai, &waitGroup, &mutex)
  }
  waitGroup.Wait()
  fmt.Print("Hasil:", nilai)
}
Code Snippet 2: Menambahkan mutex pada program

Dari hasil output program setelah dijalankan, sekarang hasilnya sesuai dengan yang diduga, bahkan ketika program dijalankan berulang-ulang tidak ada hasil data race.

Hasil:1000