简体   繁体   English

go通道死锁问题

[英]go channel deadlock issue

I just began to learn golang and not fully understand how deadlock occurs.刚开始学习golang,并没有完全理解死锁是如何发生的。 Here is an example adapted from golang playground tutorial:这是改编自 golang playground 教程的示例:

 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.您将pp作为 goroutine 启动,它开始运行,然后您调用fibonacci ,这样两者都在运行,并且一切正常。 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.然后您将fibonacci作为常规函数调用,而不是作为 goroutine,这意味着在fibonacci返回之前不会执行下一行。 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.因为fibonacci期望从它的通道接收到一些东西,所以它会阻塞直到发生——这永远不会,因为没有任何东西同时从它读取。 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):问题不在于函数的顺序,也不在于通道缓冲——问题在于,如果您想同时运行两个函数,那么首先调用的函数必须作为 g​​oroutine(或两者)运行:

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

Would work fine, because it calls fibonacci concurrently, then calls pp which can run simultaneously.会工作得很好,因为它同时调用fibonaccifibonacci ,然后调用可以同时运行的pp You can see it in action here: https://play.golang.org/p/4o3T0z5n40X你可以在这里看到它的实际效果: 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:如果您使用的是WaitGroup ,您甚至可以将它们作为 goroutine 运行,并且它们会同时运行:

 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 )通道make(chan int)隐式大小为零(参考: 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.缓冲指定大小的通道make(chan int, n) See http://golang.org/ref/spec#Send_statements for a discussion on buffered vs. unbuffered channels.有关缓冲与非缓冲通道的讨论,请参阅http://golang.org/ref/spec#Send_statements The example at http://play.golang.org/p/VZAiN1V8-P illustrates the difference. http://play.golang.org/p/VZAiN1V8-P 上的示例说明了差异。

Here, c := make(chan int) is unbuffered.这里, c := make(chan int)是无缓冲的。

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.fibonacci函数中,查看select语句。

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. select语句将保持阻塞状态,直到其中一个case被填满。 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.由于go pp(c,quit)fibonacci(c,quit)之后执行,所以没有清除通道c或发送信号quit通道的过程。 That's why function fibonacci(c,quit) will remain blocked.这就是函数fibonacci(c,quit)将保持阻塞的原因。

If you call fibonnaci first it will send the value on the channel but the receiver is not ready.如果您先调用 fibonnaci,它将在通道上发送值,但接收器尚未准备好。 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.这允许 goroutine 在没有显式锁或条件变量的情况下进行同步。

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 Go 操场上的工作代码

Always remember to wait for the go routine to finish in such situations.在这种情况下,永远记住等待 go 例程完成。 But when you call fibonnaci first it has sent the value but the receiver is not ready which leads to deadlock.但是当您首先调用 fibonnaci 时,它已经发送了值,但接收器还没有准备好,这会导致死锁。

Edit:编辑:

Because even if you wait for the go routine to finish.因为即使你等待 go 例程完成。 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 都处于睡眠状态 - 死锁!

goroutine 1 [select]: main.fibonacci(0x434080, 0x4340c0) /tmp/sandbox779301309/main.go:13 +0xc0 main.main() /tmp/sandbox779301309/main.go:34 +0x80 goroutine 1 [选择]: 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.如果您更改代码并在 for 循环的选择中创建默认情况。 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游乐场示例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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