Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid deadlock in this golang program?

Tags:

go

goroutine

Here is my program which is producing deadlock, how do I avoid it and what is the recommended pattern to handle this kind of situation.

The problem is after timeout how do I detect that there is no reader on my channel ?

var wg sync.WaitGroup

func main() {   
    wg.Add(1)
    c := make(chan int)
    go readFromChannel(c, time.After(time.Duration(2)*time.Second))
    time.Sleep(time.Duration(5) * time.Second)
    c <- 10
    wg.Wait()
}

func readFromChannel(c chan int, ti <-chan time.Time) {
    select {
    case x := <-c:
        fmt.Println("Read", x)
    case <-ti:
        fmt.Println("TIMED OUT")
    }
    wg.Done()
}
like image 464
Embedd_0913 Avatar asked May 13 '16 12:05

Embedd_0913


People also ask

What is Golang deadlock?

yourbasic.org/golang. A deadlock happens when a group of goroutines are waiting for each other and none of them is able to proceed.

How does go detect deadlock?

The main goroutine is blocked on the channel and waiting for another one to push data into the channel. However, no other goroutine is running, and it cannot be unblocked. This situation triggers the deadlock error: The deadlock detector bases its analysis of the threads created by the application.

How does deadlock occur and how it can be avoided?

(B) Deadlock occurs when both processes lock the resource simultaneously. (C) The deadlock can be resolved by breaking the symmetry of the locks. (D) The deadlock can be prevented by breaking the symmetry of the locking mechanism.


Video Answer


1 Answers

So, lets look at what's really going on in your source. You have two goroutines (there's more than two, but we're going to focus on the explicit ones), main and readFromChannel.

Lets look at what readFromChannel does:

if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group.
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group.

now Main:

adds to waitgroup 
make a channel `c`
start a goroutine `readFromChannel`
sleep for 5 seconds
send 10 to channel `c`
call wait for waitgroup

Now, lets go through the flow of execution for your code, concurrently (your code may/ may not execute in this order every time, keep that in mind)

1) wg.Add(1)
2) c := make(chan int)
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second))
#timer ti starts#
4) time.Sleep(time.Duration(5) * time.Second)
#MAIN Goroutine begins sleep
#timer ti expires#
5) case <-ti:
6) fmt.Println("TIMED OUT")
7) wg.Done()
# readFromChannel Goroutine returns #
#MAIN Goroutine exits sleep#
8) c<-10
9) ......#DEADLOCK#

Now you can guess why you got a deadlock. In go, non buffered channels will block until something happens on the other end of the channel, regardless of whether you're sending or receiving. So c <- 10 will block until something reads from the other end of c, but the goroutine you had for that has dropped out of the picture 2 seconds ago. Therefore, c blocks forever, and since main is the last goroutine left, you get a Deadlock.

How to prevent it? When using channels, ensure that there's always a receive at the other end of the channel for every send.

Using a buffered channel in this scenario can serve as a quick fix, but may fuel potential gotchas in larger repositories. For example, assuming you wrote more data to c afterward and ran go readFromChannel(c, time.After(time.Duration(2)*time.Second)) a second time. You might see:

Read D1
Read D2

or

TIMED OUT
Read D1

solely based on chance. That's probably not the behavior you'd want.

Here's how I'd resolve the deadlock:

func main() {
    wg.Add(1)
    c := make(chan int)
    go readFromChannel(c, time.After(time.Duration(2)*time.Second))
    time.Sleep(time.Duration(5) * time.Second)
    c <- 10
    wg.Wait()
}

func readFromChannel(c chan int, ti <-chan time.Time) {
        // the forloop will run forever
    loop: // **
    for {
        select {
            case x := <-c:
                    fmt.Println("Read", x)
                    break loop // breaks out of the for loop and the select **
            case <-ti:
                    fmt.Println("TIMED OUT")
            }
    }
    wg.Done()
} 

** see this answer for details

like image 166
Anfernee Avatar answered Oct 12 '22 14:10

Anfernee