为什么 Go 的 channel 可以关闭两次?
Why Go's channel can close twice?
我在做围棋练习代码的时候,遇到一个频道可以关闭两次的问题:
// jobs.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, playground")
jobs := make(chan int, 5)
done := make(chan bool)
go func() {
for {
j,more := <-jobs
fmt.Println("receive close: ", j, more)
done <- true
}
}()
close(jobs)
<- done
}
输出:
~ go run jobs.go
Hello, playground
receive close: 0 false
receive close: 0 false
但是当我手动关闭频道两次时,我得到了panic: close of closed channel
。
为什么上面的代码可以收到两次close?
一个通道只能关闭一次,试图关闭一个已关闭的通道会发生混乱。
但是receivingfrom a closed channel不受限制,从一个closed channel接收:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
一个 Go 应用程序一直运行到它的主 goroutine 运行(给定 "normal" 情况),或者从另一个角度来看:一个 Go 应用程序在它的主 goroutine 终止时终止,即 main()
函数 returns。它不会等待其他非main
goroutines 完成。
你启动了第二个 goroutine,它有一个无限的 for
循环,没有办法终止。因此,该循环将一直持续到 main()
函数——它在并发的主 goroutine 中运行——returns。由于 for
循环首先从 jobs
接收,它等待主 goroutine 关闭它(此接收操作只能然后继续)。然后主 goroutine 想要从 done
接收数据,所以要等到第二个 goroutine 向其发送一个值。然后 main goroutine 是 "free" 随时终止。你的第二个 goroutine 运行 循环可能会从 jobs
收到一个额外的值,因为它已关闭,但随后在 done
上发送将阻塞,因为没有人再从它那里接收(并且它是无缓冲的) .
通常使用 for range
从通道接收直到它关闭,如果通道关闭则退出:
for j := range jobs {
fmt.Println("received: ", j)
done <- true
}
当然这会在你的情况下导致死锁,因为循环体永远不会到达,因为没有人在 jobs
上发送任何东西,因此循环永远不会进入它的主体以在 done
这是主 goroutine 等待的。
jobs
频道没有被关闭两次。它仅在调用 close(jobs)
时关闭一次。您看到的输出是因为 goroutine 和主线程的执行方式。
当 goroutine 触发时,它不会立即启动 运行。相反,程序控制转到这部分:
close(jobs) // <--
<- done
}
和jobs
关闭。然后主线程在 done
.
的下一次接收时挂起
现在上下文切换发生并且 goroutine 启动 运行。它从关闭的 jobs
中读取,打印适当的值(false
for more
表示关闭的通道),并沿着 done
发送 true
。
但是,循环能够再次执行并且 goroutine 在 done
的下一次发送时阻塞。现在 main
再次醒来,在 done
上接收并终止程序。
Go 频道没有被两次关闭。你通过 done <- true after first print
j,more := <-jobs
fmt.Println("receive close: ", j, more)
done <- true
所以它打印了两次
receive close: 0 false
receive close: 0 false
如果您在打印前使用 done <- true 则它只会打印一次,然后关闭。
done <- true
j,more := <-jobs
fmt.Println("receive close: ", j, more)
输出:
receive close: 0 false
我在做围棋练习代码的时候,遇到一个频道可以关闭两次的问题:
// jobs.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, playground")
jobs := make(chan int, 5)
done := make(chan bool)
go func() {
for {
j,more := <-jobs
fmt.Println("receive close: ", j, more)
done <- true
}
}()
close(jobs)
<- done
}
输出:
~ go run jobs.go
Hello, playground
receive close: 0 false
receive close: 0 false
但是当我手动关闭频道两次时,我得到了panic: close of closed channel
。
为什么上面的代码可以收到两次close?
一个通道只能关闭一次,试图关闭一个已关闭的通道会发生混乱。
但是receivingfrom a closed channel不受限制,从一个closed channel接收:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
一个 Go 应用程序一直运行到它的主 goroutine 运行(给定 "normal" 情况),或者从另一个角度来看:一个 Go 应用程序在它的主 goroutine 终止时终止,即 main()
函数 returns。它不会等待其他非main
goroutines 完成。
你启动了第二个 goroutine,它有一个无限的 for
循环,没有办法终止。因此,该循环将一直持续到 main()
函数——它在并发的主 goroutine 中运行——returns。由于 for
循环首先从 jobs
接收,它等待主 goroutine 关闭它(此接收操作只能然后继续)。然后主 goroutine 想要从 done
接收数据,所以要等到第二个 goroutine 向其发送一个值。然后 main goroutine 是 "free" 随时终止。你的第二个 goroutine 运行 循环可能会从 jobs
收到一个额外的值,因为它已关闭,但随后在 done
上发送将阻塞,因为没有人再从它那里接收(并且它是无缓冲的) .
通常使用 for range
从通道接收直到它关闭,如果通道关闭则退出:
for j := range jobs {
fmt.Println("received: ", j)
done <- true
}
当然这会在你的情况下导致死锁,因为循环体永远不会到达,因为没有人在 jobs
上发送任何东西,因此循环永远不会进入它的主体以在 done
这是主 goroutine 等待的。
jobs
频道没有被关闭两次。它仅在调用 close(jobs)
时关闭一次。您看到的输出是因为 goroutine 和主线程的执行方式。
当 goroutine 触发时,它不会立即启动 运行。相反,程序控制转到这部分:
close(jobs) // <--
<- done
}
和jobs
关闭。然后主线程在 done
.
现在上下文切换发生并且 goroutine 启动 运行。它从关闭的 jobs
中读取,打印适当的值(false
for more
表示关闭的通道),并沿着 done
发送 true
。
但是,循环能够再次执行并且 goroutine 在 done
的下一次发送时阻塞。现在 main
再次醒来,在 done
上接收并终止程序。
Go 频道没有被两次关闭。你通过 done <- true after first print
j,more := <-jobs
fmt.Println("receive close: ", j, more)
done <- true
所以它打印了两次
receive close: 0 false
receive close: 0 false
如果您在打印前使用 done <- true 则它只会打印一次,然后关闭。
done <- true
j,more := <-jobs
fmt.Println("receive close: ", j, more)
输出:
receive close: 0 false