简体   繁体   中英

go channel deadlock issue

I just began to learn golang and not fully understand how deadlock occurs. Here is an example adapted from golang playground tutorial:

 package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}
func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
   // here it's good, no deadlock
     go pp(c,quit)    
     fibonacci(c, quit)
   // but if change the order of above two line:
   // fibonacci(c,quit)
   // go pp(c,quit)
   // it will deadlock
}

Why the order of the two lines above are important?

You have two functions, which need to operate concurrently in order for the channel communications to work - one must be receiving at the same time the other is sending. In this case:

 go pp(c,quit)    
 fibonacci(c, quit)

You start pp as a goroutine, it starts running, then you call fibonacci , so that both are running, and everything works. If you change it, as you suggested, to:

 fibonacci(c, quit)
 go pp(c,quit)    

Then you call fibonacci as a regular function, not as a goroutine, which means the next line will not be executed until fibonacci returns. Because fibonacci is expecting something to be receiving from its channel, it blocks until that happens - which is never, because nothing is reading from it concurrently. Hence your deadlock.

The problem isn't the order of the functions , or channel buffering - the problem is that if you want to run two functions concurrently, whichever one you call first must be run as a goroutine (or both):

 go fibonacci(c, quit)
 pp(c,quit)    

Would work fine, because it calls fibonacci concurrently, then calls pp which can run simultaneously. You can see it in action here: https://play.golang.org/p/4o3T0z5n40X

If you were using a WaitGroup , you could even run them both as goroutines and they would run concurrently:

 go fibonacci(c, quit, wg)
 go pp(c,quit, wg)    

Though in your case this isn't necessary and adds complexity.

Channels make(chan int) has implicit size zero ( ref: https://golang.org/ref/spec#Making_slices_maps_and_channels )

A channel of size zero is unbuffered. A channel of specified size make(chan int, n) is buffered. See http://golang.org/ref/spec#Send_statements for a discussion on buffered vs. unbuffered channels. The example at http://play.golang.org/p/VZAiN1V8-P illustrates the difference.

Here, c := make(chan int) is unbuffered.

If change the order of these two line

 go pp(c,quit)    
 fibonacci(c, quit)

To

fibonacci(c,quit)
go pp(c,quit)

it will cause program to deadlock. In fibonacci function, look at the select statement.

select {
    case c <- x:
        x, y = y, x+y
    case q:= <-quit:
        fmt.Println(q)
        return
}

select statement will remain blocked until one of the case is fullfilled. As go pp(c,quit) executed after fibonacci(c,quit) , so there is no process to clear the channel c or send signal to quit channel. That's why function fibonacci(c,quit) will remain blocked.

If you call fibonnaci first it will send the value on the channel but the receiver is not ready. That is the reason behind the deadlock.

Note:

By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.

Still if you want to change the order of your program to see how can we avoid the deadlock.

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)

    go func(){
       fibonacci(c, quit)
    }()
    pp(c,quit)
}

Working Code on Go playground

Always remember to wait for the go routine to finish in such situations. But when you call fibonnaci first it has sent the value but the receiver is not ready which leads to deadlock.

Edit:

Because even if you wait for the go routine to finish. It will still create a deadlock because the channels are not sync as:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   defer wg.Done()
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
   }
   quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    wg.Add(1)
    go pp(c,quit)  
    wg.Wait()
}

Output:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]: main.fibonacci(0x434080, 0x4340c0) /tmp/sandbox779301309/main.go:13 +0xc0 main.main() /tmp/sandbox779301309/main.go:34 +0x80

If you change your code and create a default case in select of for loop. Then it will satisfy that case and return while your main will exit. Never ending loop let it to wait for return in the quit case for it to return. This will work:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q, ok := <-quit:
            if ok {
                fmt.Println(q)
            }
            return
        default:
            fmt.Println("No value in any of the channel")
            return
        }
    }
}

func pp(c chan int, quit chan int) {
    for i := 0; i < 10; i++ {
        if value, ok := <-c; ok {
            fmt.Println(value)
        }
    }
    quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    go pp(c, quit)
}

Playground Example

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM