为什么 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