Knowing Deadlock and How to Overcome It in Golang
Introduction
One of the problems that occurs when using concurrent or parallel is the deadlock system. What is deadlock? Deadlock is an event where a concurrent process or goroutine waits for each other (lock) so that none of the goroutines can run. So be careful if you create an application or program that implements mutex lock and unlock using goroutines. Well we will try directly how to simulate the golang program when there is a deadlock.
Mutex Lock And Lock Creation
First we try to create an struct to hold the count data by using struct and some lock and unlock functions. And don’t forget we will also simulate this by using Mutex. Here is the code that you should create first below.
1type UserCount struct {
2 Name string
3 TotalCount int
4 sync.Mutex
5}
6
7func (c *UserCount) Lock() {
8 c.Mutex.Lock()
9}
10
11func (c *UserCount) Unlock() {
12 c.Mutex.Unlock()
13}
14
15func (c *UserCount) Change(amount int) {
16 c.TotalCount = c.TotalCount + amount
17}Creating a TransferPoint Function
In this TransferPoint function we will simulate that one User will transfer to another User so that later we also make it so that there is a deadlock.
1func TransferPoint(user1 *UserCount, user2 *UserCount, amount int) {
2 user1.Lock()
3 fmt.Println("Lock processing for user: ", user1.Name)
4 user1.Change(-amount)
5
6 time.Sleep(1 * time.Second)
7
8 user2.Lock()
9 fmt.Println("Lock processing for user: ", user2.Name)
10 user2.Change(amount)
11
12 time.Sleep(1 * time.Second)
13
14 user1.Unlock()
15 user2.Unlock()
16}Next we will create a main() function that will run a more complete simulation as below.
1func main() {
2 user1 := UserCount{
3 Name: "Ihsan",
4 TotalCount: 100,
5 }
6
7 user2 := UserCount{
8 Name: "Arif",
9 TotalCount: 100,
10 }
11
12 // call TransferPoint
13
14 fmt.Printf("User: %s count: %d\n", user1.Name, user1.TotalCount)
15 fmt.Printf("User: %s count: %d\n", user2.Name, user2.TotalCount)
16
17}1User: Ihsan count: 100
2User: Arif count: 100First Try Calling without Goroutine
In experiment 1, we will simulate calling the TransferPoint function without Goroutine it should be safe and there will be no deadlock. Why is that? Because the TransferPoint function is not called at the same time. Well, you try adding or calling the TransferPoint function after user initialization like this.
1TransferPoint(&user1, &user2, 10)
2TransferPoint(&user2, &user1, 5)1Lock processing for user : Ihsan
2Lock processing for user : Arif
3Lock processing for user : Arif
4Lock processing for user : Ihsan
5User: Ihsan count: 95
6User: Arif count: 105- Ihsan will transfer point 10 to user Arif
- Arif will transfer 5 points to user Ihsan. Then, the calculation of the total count of each of them will be like this:
- Ihsan = 100 - 10 (sent) + 5 (received) = 95
- Arif = 100 - 5 (sent) + 10 (received) = 105
The result of this calculation is true
Experiment Two Calling using Goroutine
In experiment 2, we try to simulate again that there will be deadlock when the TransferPoint function call is run using goroutine. Well, you try adding the most of the function with go and also add time.Sleep so that the running goroutine process will wait for 3 seconds, then see the code below.
1go TransferPoint(&user1, &user2, 10)
2go TransferPoint(&user2, &user1, 5)
3
4time.Sleep(3 * time.Second)1Lock processing for user: Ihsan
2Lock processing for user : Arif
3User: Ihsan count: 90
4User: Arif count: 95deadlock. Let’s try to analyze why the calculation is different.- When User Ihsan makes a transfer then a lock occurs against user Ihsan.
- At the same time User Arif will also make a transfer, so there is a lock on User Arif.
- When User Ihsan will continue the process of adding the amount to Arif, it turns out that user Arif is still
lockso the process is waiting. - Vice versa, User Arif will also continue the process of adding the amount to Ihsan but it turns out that user Ihsan is still
lockso he is waiting too. - So, this is where the
deadlockoccurs where the two processes wait for each other until an unspecified time. - In this program we specified a time of 3 seconds, then the process stops and the calculation will be wrong or messy.
Deadlock Solution
After we know how the deadlock process occurs then, how is the solution so that it does not happen like that is by doing Unlock every time there is a change in data from the user. Then, we will change the TransferPoint function as below.
1func TransferPoint(user1 *UserCount, user2 *UserCount, amount int) {
2 user1.Lock()
3 fmt.Println("Lock processing for user: ", user1.Name)
4 user1.Change(-amount)
5 user1.Unlock()
6
7 time.Sleep(1 * time.Second)
8
9 user2.Lock()
10 fmt.Println("Lock processing for user : ", user2.Name)
11 user2.Change(amount)
12 user2.Unlock()
13
14 time.Sleep(1 * time.Second)
15}1Lock processing for user : Arif
2Lock processing for user : Ihsan
3Lock processing for user : Arif
4Lock processing for user : Ihsan
5User: Ihsan count: 95
6User: Arif count: 105