Go channels and deadlock

12,453

Solution 1

Go channels created with make(chan int) are not buffered. If you want a buffered channel (that won't necessarily block), make it with make(chan int, 2) where 2 is the size of the channel.

The thing about unbuffered channels is that they are also synchronous, so they always block on write as well as read.

The reason it deadlocks is that your first goroutine is waiting for its c2 <- i to finish while the second one is waiting for c1 <- i to finish, because there was an extra thing in c1. The best way I've found to debug this sort of thing when it happens in real code is to look at what goroutines are blocked and think hard.

You can also sidestep the problem by only using synchronous channels if they're really needed.

Solution 2

nmichaels is right on with his answer, but I thought I'd add that there are ways to figure out where you're deadlocking when debugging a problem like this.

A simple one is if you're on a Unix-like OS, run the command

kill -6 [pid]

This will kill the program and give a stack trace for each goroutine.

A slightly more involved way is to attach gdb.

gdb [executable name] [pid]

You can examine the stack and variables of the active goroutine as normal, but there's no easy way to switch goroutines that I know of. You can switch OS threads in the usual way, but that might not be enough to help.

Solution 3

to prevent the channel from overflowing, you can ask for the channel's current capacity and dry it before writing again.

in my case, the game takes place at 60fps and the mouse moves much faster, so it is always good to check that the channel has been cleared before writing again.

notice that the previous data is lost

package main

import (
    "fmt"
)

func main() {
    // you must specify the size of the channel, 
    // even for just one element, or the code doesn't work
    ch := make( chan int, 1 )
    fmt.Printf("len: %v\n", len(ch))
    fmt.Printf("cap: %v\n\n", cap(ch))

    ch <- 1

    for i := 0; i != 100; i += 1 {
        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))

        if cap( ch ) == 1 {
            <- ch
        }

        ch <- i

        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))
    }
    fmt.Printf("end!\n")
}
Share:
12,453

Related videos on Youtube

mkm
Author by

mkm

Updated on June 04, 2022

Comments

  • mkm
    mkm almost 2 years

    I'm trying to understand the Go language. I tried to create two goroutines that chain the flow between them using two channels:

    func main() {
    c1 := make(chan int)
    c2 := make(chan int)
    
    go func() {
        for i := range c1{
            println("G1 got", i)
            c2 <- i
        }
    }()
    
    go func() {
        for i := range c2 {
            println("G2 got", i)
            c1 <- i
        }
    }()
    
    
    c1 <- 1
    
    time.Sleep(1000000000 * 50)
    }
    

    As expected this code prints:

     G1 got 1
     G2 got 1
     G1 got 1
     G2 got 1
     ....
    

    Until the main function exits.

    But if I send another value to one of the channels from main, it suddenly blocks:

    func main() {
    c1 := make(chan int)
    c2 := make(chan int)
    
    go func() {
        for i := range c1{
            println("G1 got", i)
            c2 <- i
        }
    }()
    
    go func() {
        for i := range c2 {
            println("G2 got", i)
            c1 <- i
        }
    }()
    
    
    c1 <- 1
    
    time.Sleep(1000000000 * 1)
    
    c1 <- 2
    
    time.Sleep(1000000000 * 50)
    }
    

    It outputs

    G1 got 1
    G2 got 1
    G1 got 1
    G2 got 1
    G1 got 2
    

    and then blocks until the main ends.

    The value "2" sent to c1 arrives to the first goroutie, which sends it to c2, but the second goroutine never receives.

    (Using buffered channels with size 1 (either c1 or c2) works in this example)

    Why does it happen? When this happens in real code, how can I debug it?

  • mkm
    mkm almost 13 years
    The same thing can happen to buffered channels when the buffer fills up?
  • mkm
    mkm almost 13 years
    thank you for the hints! Is there a way to print the stack traces without terminating the process? Perhaps, some runtime function called from inside the process?
  • nmichaels
    nmichaels almost 13 years
    @ithkuil: In this case, all the buffers for all the channels would have to fill up, so your example wouldn't cause it. If your program is always putting more into channels than it's taking out, you will have problems eventually.
  • Edwin Biemond
    Edwin Biemond almost 13 years
    Yes, you can import runtime/debug and call either Stack to get a string or PrintStack to just dump to stdout. I suppose you could still use this to debug deadlocks if your application had a separate goroutine handling signals from os/signals.
  • mdup
    mdup over 10 years
    Thanks for kill -6: easiest way to find where a channel is blocked!
  • Timmmm
    Timmmm over 8 years
    So you just have to pick an arbitrarily large buffer size and hope that it never fills up? Doesn't seem very reliable.