Deadlock and Default Case in Select Statement in Golang


Golang provides a powerful feature known as the "select" statement that allows developers to handle multiple channels simultaneously. It is one of the most important features that make Golang a highly concurrent and efficient programming language. However, the select statement can lead to two common issues - deadlock and default case. In this article, we will discuss what they are, how to avoid them, and how to handle them.

Deadlock in Select Statement

Deadlock is a common problem in concurrent programming that occurs when two or more threads are waiting for each other to release a resource that they need to proceed. In the context of Golang's select statement, deadlock occurs when none of the cases in the select statement can proceed, and there are no default cases.

Example

Here is an example of how deadlock can occur in the select statement −

package main

import (
   "fmt"
)
   
func main() {
   ch1 := make(chan int)
   ch2 := make(chan int)
   
   go func() {
      ch1 <- 1
   }()
   
   go func() {
      ch2 <- 2
   }()
   
   select {
   case <-ch1:
      fmt.Println("Received from ch1")
   case <-ch2:
      fmt.Println("Received from ch2")
   }
}

Output

Received from ch2

In the above code, we have created two channels ch1 and ch2 and launched two goroutines to send values to them. However, the select statement is waiting for a value from either ch1 or ch2, but none of them is ready yet. Therefore, the select statement blocks indefinitely, and the program enters a deadlock state.

To avoid deadlock in the select statement, you can use a default case that will execute if none of the other cases can proceed. Here is an updated version of the above code that uses a default case −

Example 2

package main

import (
   "fmt"
)

func main() {
   ch1 := make(chan int)
   ch2 := make(chan int)

   go func() {
      ch1 <- 1
   }()

   go func() {
      ch2 <- 2
   }()

   select {
      case <-ch1:
         fmt.Println("Received from ch1")
      case <-ch2:
         fmt.Println("Received from ch2")
      default:
         fmt.Println("No data received")
   }
}

Output

No data received

In this updated version, the default case will execute if neither of the channels is ready, preventing the program from entering a deadlock state.

Default Case in Select Statement

The default case in the select statement is executed when none of the other cases can proceed. It is often used to implement non-blocking operations, where you want to perform an action only if a value is available on a channel; otherwise, you want to continue with the next line of code.

Example

Here is an example of how the default case can be used in the select statement −

package main

import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int)

   go func() {
      time.Sleep(time.Second)
      ch <- 1
   }()

   select {
      case <-ch:
         fmt.Println("Received data from channel")
      default:
         fmt.Println("No data received from channel")
   }
   
   fmt.Println("Program continues...")
}

Output

No data received from channel
Program continues...

In this example, we have created a channel ch and launched a goroutine to send a value to it after one second. The select statement is waiting for a value from the channel, but since the value is not available immediately, the default case is executed, and the program continues with the next line of code.

Conclusion

The select statement is a powerful feature in Golang that allows developers to handle multiple channels simultaneously. However, it can lead to deadlock and default case issues if not used correctly. By using default cases in the select statement, you can prevent your program from entering a deadlock state and continue with the next line of code if no value is available on a channel.

Updated on: 17-Apr-2023

419 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements