Go 为什么在延迟语句中关闭通道会引起恐慌?
在下面的示例中,go例程将值泵送到无缓冲通道中,主函数对其进行迭代Go 为什么在延迟语句中关闭通道会引起恐慌?,go,goroutine,Go,Goroutine,在下面的示例中,go例程将值泵送到无缓冲通道中,主函数对其进行迭代 主程序包 进口( “fmt” “strconv” ) var chanStr chan字符串 func main(){ 加油泵() fmt.Println(“迭代…”) 对于val:=range chanStr{ fmt.Printf(“从通道\n获取的val:%s”,val) } } func泵(){ 延迟关闭(chanStr) chanStr=制造(成串) 对于i:=1;i您发布的代码有一个死锁场景。正如Flimzy指出的,
主程序包
进口(
“fmt”
“strconv”
)
var chanStr chan字符串
func main(){
加油泵()
fmt.Println(“迭代…”)
对于val:=range chanStr{
fmt.Printf(“从通道\n获取的val:%s”,val)
}
}
func泵(){
延迟关闭(chanStr)
chanStr=制造(成串)
对于i:=1;i您发布的代码有一个死锁场景。正如Flimzy指出的,您可能没有发布相同的代码
以下是应该可以使用的更新代码:
package main
import (
"fmt"
"strconv"
)
func main() {
chanStr := make(chan string)
go pump(chanStr)
fmt.Println("iterating ...")
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}
func pump(ch chan string) {
defer close(ch)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
ch <- "val" + strconv.Itoa(i)
}
//close(chanStr)
}
主程序包
进口(
“fmt”
“strconv”
)
func main(){
chanStr:=make(chan字符串)
go泵(chanStr)
fmt.Println(“迭代…”)
对于val:=range chanStr{
fmt.Printf(“从通道\n获取的val:%s”,val)
}
}
func泵(ch chan管柱){
延迟关闭(ch)
对于i:=1;i主go例程可能会在创建之前从通道中读取数据。这就是您的数据竞争
应在开始围棋程序之前创建通道
修复:您的代码非常快速,有不同的方式:
在goroutine实际初始化通道之前,您有可能(事实上,很可能)在for val
循环中开始读取通道,从而导致死锁
iterating ...
pumping seq 1 into channel
fatal error: all goroutines are asleep - deadlock!
事实上,这是我观察到的在本地或操场上执行代码的唯一行为
如果我加上一个延迟
fmt.Println("iterating ...")
time.Sleep(10 * time.Millisecond) // Delay ensures the channel has been created
for val := range chanStr {
然后我确实观察到了你的行为:
iterating ...
pumping seq 1 into channel
fetched val: val1 from channel
pumping seq 2 into channel
pumping seq 3 into channel
fetched val: val2 from channel
fetched val: val3 from channel
pumping seq 4 into channel
pumping seq 5 into channel
fetched val: val4 from channel
fetched val: val5 from channel
panic: close of nil channel
原因是您正在调用close(chanStr)
,而chanStr
仍然为零。如果在创建频道后调用您的defer
:
func pump() {
chanStr = make(chan string)
defer close(chanStr)
你会解决那个问题的
要解决这两个争用,您需要在调用goroutine之前初始化通道。完整代码:
package main
import (
"fmt"
"strconv"
)
var chanStr chan string
func main() {
chanStr = make(chan string)
go pump(chanStr)
fmt.Println("iterating ...")
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}
func pump(chanStr chan string) {
defer close(chanStr)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
chanStr <- "val" + strconv.Itoa(i)
}
}
在这种情况下,延迟函数是对chanStr
的闭包,因此chanStr
的计算会延迟到实际执行。在这个版本中,当延迟函数执行时,chanStr
不再为零,因此不会出现恐慌。比赛是主go例程从一个将被改变的chan值中读取的地方d由后台go例程执行。提前创建通道。由于通道只是一个引用,而泵()
保持对该引用的关闭,当调用延迟关闭(chanStr)
时,它不应该是nil
通道,对吗?尽管通道不是引用。以及泵()
是否为闭包无关紧要。close(chanStr)
会立即计算,即使函数退出后才会执行close
函数本身。我用另一个示例更新了答案,以说明问题在于chanStr
的早期计算。“发生这种情况是因为通道在主函数可以使用数据之前关闭”--不,这是不准确的。发生这种情况是因为延迟关闭(chanStr)
是在定义chanStr
之前被调用的。您会注意到,在失败的运行中,数据是在死锁之前从通道中消耗的。这部分与他发布的代码无关,这是为什么从关闭的通道读取会导致死锁的答案。这部分是在我运行他的代码并注意到死锁之前添加的。Readin但是,通道中的g不会引起恐慌。关闭通道会引起恐慌。OP自身的输出显示通道中的成功读取。我认为在帖子中提到了类似的内容,但你是对的。我得到了更正。顺便说一句,你的解决方案会解决问题…只是不是因为你解释的原因:)这是一个race、 但不是OP询问的那一个。:)这个特定的竞争导致死锁,而不是关闭一个零通道。@Flimzy true。但是我在go例程之前创建通道的修复方法解决了这两个竞争条件。不值得扭曲代码来处理可能更改的通道值。相反,修复代码,使通道值永远不会更改。是的,这与我在回答中提出的解决方案相同。
package main
import (
"fmt"
"strconv"
)
var chanStr chan string
func main() {
chanStr = make(chan string)
go pump(chanStr)
fmt.Println("iterating ...")
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}
func pump(chanStr chan string) {
defer close(chanStr)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
chanStr <- "val" + strconv.Itoa(i)
}
}
package main
import (
"fmt"
"strconv"
"time"
)
var chanStr chan string
func main() {
go pump()
fmt.Println("iterating ...")
time.Sleep(10 * time.Millisecond)
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}
func pump() {
defer func() {
close(chanStr)
}()
chanStr = make(chan string)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
chanStr <- "val" + strconv.Itoa(i)
}
}