11

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()
}
Embedd_0913
  • 16,125
  • 37
  • 97
  • 135
  • 1
    Does this answer your question? [How to implement non-blocking write to an unbuffered channel?](https://stackoverflow.com/questions/48953236/how-to-implement-non-blocking-write-to-an-unbuffered-channel) – Amin Shojaei Mar 11 '22 at 07:16

9 Answers9

16

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

Anfernee
  • 1,445
  • 1
  • 15
  • 26
  • 1
    This is one of the most useful answers I've read in some time. Just to make sure I'm following: the "fix" works because it keeps the receiver channel going even after a time out. So `wg.Done()` (and terminating the `main` go routine) will only ever happen if something was read in from `c`, right? – Alex Moore-Niemi Aug 31 '16 at 16:55
  • 1
    Right, but to clear up a bit, it keeps the **goroutine** running. – Anfernee Aug 31 '16 at 17:04
1

You have an unbuffered channel. According to the docs:

If the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer

By changing the channel to being buffered, we can avoid deadlock.

c := make(chan int, 10) // holds 10 ints

I also suggest reading https://golang.org/doc/effective_go.html#channels, it's got some good stuff in there related to channels.

william.taylor.09
  • 2,145
  • 10
  • 17
0

Your problem is that you are using select statement but you are not using inside a goroutine.

go func() {
    for {
        select {
        case x := <-c:
            fmt.Println("Read", x)
        case <-ti:
            fmt.Println("TIMED OUT")
        }
    }
}()

Getting the values out of different concurrently executing goroutines can be accomplished with the select keyword, which closely resembles the switch control statement and is sometimes called the communications switch.

Using a send operation in a select statement with a default case guarantees that the send will be non-blocking! If there are no cases, the select blocks execution forever.

https://play.golang.org/p/Ai1ggveb4s

Endre Simo
  • 11,330
  • 2
  • 40
  • 49
0

This is an older question, but i'm diving deep into learning channels myself and found this here.

I think you just need to close the channel after your done sending on it?

Code:

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
    close(c) // <- CLOSE IT HERE
    wg.Wait()
}
Matt Fiocca
  • 1,533
  • 13
  • 21
0

To avoid deadlock here is some general advice from Deadlocks: the dark side of concurrency

  • Channel deadlock, Don’t send and receive to the same channel in the same goroutine
  • Read lock, Don’t take a read lock twice in the same goroutine
  • Release the lock as soon as possible
  • Objects which contain locks that call each other methods are a deadlock waiting to happen
  • Care total lock ordering
  • Access only read variables outside the lock
zangw
  • 43,869
  • 19
  • 177
  • 214
0

You can add a buffer to the c chan, like this:

var wg sync.WaitGroup

func main() {   
    wg.Add(1)
    c := make(chan int, 1)
    go readFromChannel(c, time.After(time.Duration(2)*time.Second))
    time.Sleep(time.Duration(5) * time.Second)
    c <- 10 // never blocks, because of the buffer
    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()
}
Amin Shojaei
  • 5,451
  • 2
  • 38
  • 46
0

a write operation(c <- 10) to an unbuffered channel will be blocked until it there is a read operation(case x := <-c:).

Your timeout(2s) is smaller than sleep duration(5s) and because of this channel c will never be read in the select block.

I would recommend you to read chapter 8 & 9 of this book

Mohammed
  • 637
  • 13
  • 26
0

This program leads to a deadlock because the wg.Wait() call in the main function will never return.

When main is executed, it creates a WaitGroup and adds one to the count. It then creates a channel c and launches a goroutine to read from that channel using the readFromChannel function. The readFromChannel function uses a select statement to either read from the channel or wait for a timeout. Once it receives a value from the channel or times out, it calls wg.Done() to signal that it has completed its task.

Meanwhile, the main function sleeps for 5 seconds and then sends a value to the channel c. However, since the readFromChannel function is waiting for a value on that same channel, the send operation will block until the receiver is ready to receive the value. However, the receiver is blocked waiting for the wg.Wait() call to return, so neither can proceed and a deadlock occurs.

To fix this, you can remove the wg and wg.Wait() calls altogether, as they are not needed in this program. Alternatively, you can move the wg.Done() call to the end of the readFromChannel function, after the select statement.

-1
func main() {

    wg.Add(1)
    c := make(chan int)
    go func() {
        c <- 10

    }()
    go readFromChannel(c, time.After(time.Duration(2)*time.Second))
    time.Sleep(time.Duration(5) * time.Second)

    wg.Wait()

}//This will read without 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)

    go func() {
        c <- 10

    }()
    wg.Wait()

}//Time out without deadlock