Atomic Package Pada Go

Kalau semisalnya melakukan increment atau decrement pada variabel menggunakan channel atau sebuah variabel yang digunakan bersamaan pada satu waktu yang sama, goroutines tidak akan sync/synchronized dan akan menghasilkan output yang keliru.

Operasi Atomic biasanya diimplementasikan ke hardware level. Yang artinya kita bisa membuat suatu variabel Atomic yang digunakan bersamaan dengan banyak goroutines untuk merubah valuenya secara benar. Go memiliki package atomic yang membantu sinkronisasi saat melakukan konkurensi.

Package Atomic berada di sync/atomic.

Di package Atomic ada banyak function built-in yang bisa digunakan, ada load, store dan operasi penambahan untuk tipe data int32, int64, uint32, uint64 dan lain-lain. Karena int hanya tipe data yang primitif yang bisa disinkronkan secara Atomic.

Sebagai contoh ada kodingan yang tanpa menggunakan Atomic.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var counter int64
	var wg sync.WaitGroup

	for i := 1; i <= 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter++
		}()
	}
	wg.Wait()

	fmt.Println(counter)
}

Kodingan di atas akan menjalankan 1000 goroutines yang dimana akan melakukan operasi increment pada variabel counter. Namun hasil output tidak sesuai dengan apa yang diinginkan, nilai pada variabel counter yang diinginkan adalah 1000, namun output menghasilkan 931.

Counter : 931

Disini terjadi yang namanya Race Condition. Race condition adalah kondisi dua atau lebih goroutine mengakses data yang sama pada waktu yang bersamaan juga. Ketika hal ini terjadi, nilai pada data tersebut akan menjadi kacau dan tidak sesuai apa yang diinginkan.

Dan masalah ini terjadi ketika ada proses yang berjalan secara asynchronous atau concurrency programming contohnya goroutines ini.

Lalu bagaimana cara mengatasinya? Ada dua cara yang bisa digunakan untuk mengatasinya, yang pertama bisa menggunakan package sync/mutex dan menggunakan package Atomic sync/atomic.

Mutex melakukan pengubahan sebuah data menjadi eksklusif, artinya hanya dapat dikonsumsi oleh satu buah goroutine saja. Cara kerjanya jika ada sebuah goroutine yang sudah mengakses suatu data, maka goroutine selanjutnya akan menunggu hingga goroutine yang sedang mengakses data tersebut selesai.

Oke langsung saja ke kodingannya:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var counter int64
	var wg sync.WaitGroup
	var mutex sync.Mutex

	for i := 1; i <= 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mutex.Lock()
			counter++
			mutex.Unlock()
		}()
	}
	wg.Wait()

	fmt.Println("Counter :", counter)
}

Dari contoh diatas kita melakukan yang namanya locking mutex.Lock() untuk proses increment dan melakukan locking pada baris 17. Dan jangan lupa juga untuk buka kembali menggunakan mutex.Unlock() di baris 19.

Counter : 1000

Dan hasil output akan sesuai apa yang diinginkan yaitu 1000.

Cara yang kedua bisa menggunakan package sync/atomic. Dengan kodingan seperti di bawah ini:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int64
	var wg sync.WaitGroup

	for i := 1; i <= 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			atomic.AddInt64(&counter, 1)
		}()
	}
	wg.Wait()

	fmt.Println(counter)
}

Dari contoh kodingan di atas kita menggunakan package atomic di baris 17 dengan menggunakan function addInt64(addr *int64, delta int64).

Di parameter pertama kita passing pointer variabel counter dan di parameter kedua adalah deltanya.

Jadi ya begitulah bagaimana cara mengatasi Race Condition pada sisi programming menggunakan bahasa pemrograman Go.

Referensi yang bisa dibaca: