去通道死锁问题
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
}
为什么上面两行的顺序很重要?
如果您先调用 fibonnaci,它会在通道上发送值,但接收器尚未准备好。这就是僵局背后的原因。
注:
By default, sends and receives block until the other side is ready.
This allows goroutines to synchronize without explicit locks or
condition variables.
如果您想更改程序的顺序,看看我们如何避免死锁。
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 例程完成。但是当你首先调用 fibonnaci 时它已经发送了值但是接收者还没有准备好导致死锁。
编辑:
因为即使你等待 go routine 完成。它仍然会造成死锁,因为通道不同步为:
主要包
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()
}
输出:
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
如果您更改代码并在 for 循环的 select 中创建默认情况。然后它将满足这种情况和 return 而你的 main 将退出。永无止境的循环让它在退出的情况下等待 return 到 return。这将起作用:
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)
}
频道 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)
将保持阻塞的原因。
您有两个功能,它们需要同时运行才能使通道通信正常工作 - 一个必须同时接收另一个发送。在这种情况下:
go pp(c,quit)
fibonacci(c, quit)
你将 pp
作为 goroutine 启动,它启动 运行ning,然后你调用 fibonacci
,这样两者都是 运行ning,一切正常。如果按照您的建议将其更改为:
fibonacci(c, quit)
go pp(c,quit)
然后你调用fibonacci
作为一个常规函数,而不是作为一个goroutine,这意味着下一行直到fibonacci
returns才会被执行。因为 fibonacci
期望从它的通道接收到一些东西,它会阻塞直到那发生——这永远不会发生,因为没有任何东西同时从它读取。因此你的僵局。
问题不在于 函数 的顺序或通道缓冲 - 问题是如果你想同时 运行 两个函数,无论你选择哪个首先调用必须是 运行 作为 goroutine(或两者):
go fibonacci(c, quit)
pp(c,quit)
可以正常工作,因为它同时调用 fibonacci
,然后调用 pp
,后者可以同时调用 运行。你可以在这里看到它的实际效果:https://play.golang.org/p/4o3T0z5n40X
如果你使用 WaitGroup
,你甚至可以 运行 它们都作为 goroutines 并且它们会 运行 同时:
go fibonacci(c, quit, wg)
go pp(c,quit, wg)
尽管在您的情况下这不是必需的并且会增加复杂性。
刚开始学习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
}
为什么上面两行的顺序很重要?
如果您先调用 fibonnaci,它会在通道上发送值,但接收器尚未准备好。这就是僵局背后的原因。
注:
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
如果您想更改程序的顺序,看看我们如何避免死锁。
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 例程完成。但是当你首先调用 fibonnaci 时它已经发送了值但是接收者还没有准备好导致死锁。
编辑:
因为即使你等待 go routine 完成。它仍然会造成死锁,因为通道不同步为:
主要包
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()
}
输出:
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
如果您更改代码并在 for 循环的 select 中创建默认情况。然后它将满足这种情况和 return 而你的 main 将退出。永无止境的循环让它在退出的情况下等待 return 到 return。这将起作用:
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)
}
频道 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)
将保持阻塞的原因。
您有两个功能,它们需要同时运行才能使通道通信正常工作 - 一个必须同时接收另一个发送。在这种情况下:
go pp(c,quit)
fibonacci(c, quit)
你将 pp
作为 goroutine 启动,它启动 运行ning,然后你调用 fibonacci
,这样两者都是 运行ning,一切正常。如果按照您的建议将其更改为:
fibonacci(c, quit)
go pp(c,quit)
然后你调用fibonacci
作为一个常规函数,而不是作为一个goroutine,这意味着下一行直到fibonacci
returns才会被执行。因为 fibonacci
期望从它的通道接收到一些东西,它会阻塞直到那发生——这永远不会发生,因为没有任何东西同时从它读取。因此你的僵局。
问题不在于 函数 的顺序或通道缓冲 - 问题是如果你想同时 运行 两个函数,无论你选择哪个首先调用必须是 运行 作为 goroutine(或两者):
go fibonacci(c, quit)
pp(c,quit)
可以正常工作,因为它同时调用 fibonacci
,然后调用 pp
,后者可以同时调用 运行。你可以在这里看到它的实际效果:https://play.golang.org/p/4o3T0z5n40X
如果你使用 WaitGroup
,你甚至可以 运行 它们都作为 goroutines 并且它们会 运行 同时:
go fibonacci(c, quit, wg)
go pp(c,quit, wg)
尽管在您的情况下这不是必需的并且会增加复杂性。