[英]go channel deadlock issue
刚开始学习golang,并没有完全理解死锁是如何发生的。 这是改编自 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
}
为什么上面两行的顺序很重要?
您有两个函数,它们需要同时运行才能使通道通信正常工作——一个必须在接收的同时另一个在发送。 在这种情况下:
go pp(c,quit)
fibonacci(c, quit)
您将pp
作为 goroutine 启动,它开始运行,然后您调用fibonacci
,这样两者都在运行,并且一切正常。 如果您按照您的建议将其更改为:
fibonacci(c, quit)
go pp(c,quit)
然后您将fibonacci
作为常规函数调用,而不是作为 goroutine,这意味着在fibonacci
返回之前不会执行下一行。 因为fibonacci
期望从它的通道接收到一些东西,所以它会阻塞直到发生——这永远不会,因为没有任何东西同时从它读取。 因此你的僵局。
问题不在于函数的顺序,也不在于通道缓冲——问题在于,如果您想同时运行两个函数,那么首先调用的函数必须作为 goroutine(或两者)运行:
go fibonacci(c, quit)
pp(c,quit)
会工作得很好,因为它同时调用fibonacci
那fibonacci
,然后调用可以同时运行的pp
。 你可以在这里看到它的实际效果: https : //play.golang.org/p/4o3T0z5n40X
如果您使用的是WaitGroup
,您甚至可以将它们作为 goroutine 运行,并且它们会同时运行:
go fibonacci(c, quit, wg)
go pp(c,quit, wg)
尽管在您的情况下,这不是必需的,并且会增加复杂性。
通道make(chan int)
隐式大小为零(参考: https : //golang.org/ref/spec#Making_slices_maps_and_channels )
大小为零的通道是无缓冲的。 缓冲指定大小的通道make(chan int, n)
。 有关缓冲与非缓冲通道的讨论,请参阅http://golang.org/ref/spec#Send_statements 。 http://play.golang.org/p/VZAiN1V8-P 上的示例说明了差异。
这里, c := make(chan int)
是无缓冲的。
如果改变这两行的顺序
go pp(c,quit)
fibonacci(c, quit)
到
fibonacci(c,quit)
go pp(c,quit)
它会导致程序死锁。 在fibonacci
函数中,查看select
语句。
select {
case c <- x:
x, y = y, x+y
case q:= <-quit:
fmt.Println(q)
return
}
select
语句将保持阻塞状态,直到其中一个case
被填满。 由于go pp(c,quit)
在fibonacci(c,quit)
之后执行,所以没有清除通道c
或发送信号quit
通道的过程。 这就是函数fibonacci(c,quit)
将保持阻塞的原因。
如果您先调用 fibonnaci,它将在通道上发送值,但接收器尚未准备好。 这就是僵局背后的原因。
笔记:
默认情况下,发送和接收阻塞,直到对方准备好。 这允许 goroutine 在没有显式锁或条件变量的情况下进行同步。
还是如果你想改变你的程序的顺序,看看我们如何避免死锁。
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)
}
Go 操场上的工作代码
在这种情况下,永远记住等待 go 例程完成。 但是当您首先调用 fibonnaci 时,它已经发送了值,但接收器还没有准备好,这会导致死锁。
编辑:
因为即使你等待 go 例程完成。 它仍然会造成死锁,因为通道不同步:
包主
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()
}
输出:
致命错误:所有 goroutine 都处于睡眠状态 - 死锁!
goroutine 1 [选择]: main.fibonacci(0x434080, 0x4340c0) /tmp/sandbox779301309/main.go:13 +0xc0 main.main() /tmp/sandbox779301309/main.go:34 +0x80
如果您更改代码并在 for 循环的选择中创建默认情况。 然后它将满足这种情况并返回,而您的主要将退出。 永不结束循环让它在退出情况下等待返回以使其返回。 这将起作用:
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)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.