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