pemrograman

Mengenal Deadlock Dan Cara Mengatasinya Pada Golang

Pengenalan

Salah satu masalah yang terjadi ketika menggunakan concurrent atau paralel yaitu sistem deadlock. Apa itu deadlock? Deadlock adalah kejadian dimana sebuah proses concurrent atau goroutine saling menunggu (lock) sehingga tidak ada satupun goroutine yang bisa berjalan. Maka hati-hati bagi Anda jika membuat aplikasi atau program yang mengimplementasikan mutex lock and unlock dengan menggunakan goroutine. Baiklah kita akan coba saja langsung bagaimana simulasi pada program golang ketika terjadi deadlock.

Pembuatan Mutex Lock And Lock

Pertama kali kita coba buat struct untuk menampung hitungan data dengan menggungakan struct dan beberapa fungsi lock and unlock. Dan jangan lupa juga kita akan mensimulasikan ini dengan penggunaan Mutex. Berikut kode yang harus pertama kali kamu buat dibawah ini.

type UserCount struct {
	Name       string
	TotalCount int
	sync.Mutex
}

func (c *UserCount) Lock() {
	c.Mutex.Lock()
}

func (c *UserCount) Unlock() {
	c.Mutex.Unlock()
}

func (c *UserCount) Change(amount int) {
	c.TotalCount = c.TotalCount + amount
}

Membuat Fungsi TransferPoint

Pada fungsi TransferPoint ini kita akan mensimulasikan bahwa User satu akan melakukan transfer terhadap User yang lain sehingga nanti kita juga buat agar terjadi deadlock.

func TransferPoint(user1 *UserCount, user2 *UserCount, amount int) {
	user1.Lock()
	fmt.Println("Lock processing for user : ", user1.Name)
	user1.Change(-amount)

	time.Sleep(1 * time.Second)

	user2.Lock()
	fmt.Println("Lock processing for user : ", user2.Name)
	user2.Change(amount)

	time.Sleep(1 * time.Second)

	user1.Unlock()
	user2.Unlock()
}

Selanjutnya kita akan membuat main() fungsi yang akan menjalankan simulasinya lebih lengkap seperti dibawah ini.

func main() {
	user1 := UserCount{
		Name:       "Ihsan",
		TotalCount: 100,
	}

	user2 := UserCount{
		Name:       "Arif",
		TotalCount: 100,
	}

  // call TransferPoint

	fmt.Printf("User: %s count: %d\n", user1.Name, user1.TotalCount)
	fmt.Printf("User: %s count: %d\n", user2.Name, user2.TotalCount)

}

Lalu, jalankan proses main tersebut sehingga akan mengeluarkan seperti dibawah ini.

User: Ihsan count: 100
User: Arif count: 100

Percobaan Pertama Memanggil tanpa Goroutine

Pada percobaan 1, kita akan melakukan simulasi memanggil fungsi TransferPoint tanpa Goroutine seharusnya ini akan aman-aman saja dan tidak akan terjadi deadlock. Kenapa bisa begitu? karena fungsi TransferPoint ini tidak dipanggil dalam waktu yang bersamaan. Baik, Anda coba tambahkan atau panggil fungsi TransferPoint setelah inisialisasi user seperti ini.

TransferPoint(&user1, &user2, 10)
TransferPoint(&user2, &user1, 5)

Maka, hasilnya akan seperti ini

Lock processing for user :  Ihsan
Lock processing for user :  Arif
Lock processing for user :  Arif
Lock processing for user :  Ihsan
User: Ihsan count: 95
User: Arif count: 105

User Ihsan dan Arif memiliki point yang sama yaitu 100, lalu mereka saling transfer point yang mana sebagai berikut:

  • Ihsan akan melakukan transfer point 10 kepada user Arif
  • Arif akan melakukan transfer point 5 kepada user Ihsan Maka, perhitungan total count-nya masing-masing itu akan seperti ini:
  • Ihsan = 100 - 10 (sent) + 5 (receive) = 95
  • Arif = 100 - 5 (sent) + 10 (receive) = 105

Hasil perhitungan ini adalah benar

Percobaan Dua Memanggil menggunakan Goroutine

Pada percobaan 2, kita coba simulasikan kembali yang mana ini akan terjadi deadlock saat pemanggilan fungsi TransferPoint itu berjalan menggunakan goroutine. Baik, Anda coba tambahkan paling dari fungsi tersebut dengan go dan juga tambahkan time.Sleep agar proses goroutine yang sedang berjalan akan kita tunggu selama 3 detik, maka lihat kode dibawah ini.

go TransferPoint(&user1, &user2, 10)
go TransferPoint(&user2, &user1, 5)

time.Sleep(3 * time.Second)

Maka apa yang akan terjadi hasil program ketika dijalankan?

Lock processing for user :  Ihsan
Lock processing for user :  Arif
User: Ihsan count: 90
User: Arif count: 95

Perhitungan dari fungsi tersebut menjadi salah dan kenapa ini terjadi yaitu karena deadlock. Baik kita coba bedah kenapa perhitungannya berbeda.

  • Saat User Ihsan melakukan transfer maka terjadi lock terhadap user Ihsan.
  • Disaat waktu yang bersamaan User Arif juga akan melakukan transfer maka terjadi lock terhadap User Arif.
  • Ketika User Ihsan akan melanjutkan proses menambahkan amount kepada Arif, ternyata user Arif dalam keadaan masih lock sehingga proses tersebut ditunggu.
  • Begitu juga sebaliknya User Arif juga akan melanjutkan proses menambahkan amount terhadap Ihsan tetapi ternyata user Ihsan masih lock sehingga menunggu juga.
  • Maka, disinilah terjadi deadlock yang mana kedua proses tersebut saling menunggu sampai waktu yg tidak ditentukan.
  • Pada program ini kita ditentukan dalam waktu 3 detik, maka proses-nya pun berhenti dan perhitungannya akan menjadi salah atau kacau.

Solusi Deadlock

Setelah kita tahu bagaimana proses deadlock itu terjadi maka, bagaimana solusinya agar tidak terjadi seperti itu yaitu dengan cara melakukan Unlock setiap ada perubahan data dari user tersebut. Maka, kita akan ubah fungsi TransferPoint seperti dibawah ini.

func TransferPoint(user1 *UserCount, user2 *UserCount, amount int) {
	user1.Lock()
	fmt.Println("Lock processing for user : ", user1.Name)
	user1.Change(-amount)
	user1.Unlock()

	time.Sleep(1 * time.Second)

	user2.Lock()
	fmt.Println("Lock processing for user : ", user2.Name)
	user2.Change(amount)
	user2.Unlock()

	time.Sleep(1 * time.Second)
}

Saat dijalankan program tersebut akan menghasilkan keluaran yang sama dengan yang percobaan 1.

Lock processing for user :  Arif
Lock processing for user :  Ihsan
Lock processing for user :  Arif
Lock processing for user :  Ihsan
User: Ihsan count: 95
User: Arif count: 105
comments powered by Disqus