本文是探讨的是"在Go语言中,我们是否可以使用读取管道时的第二个返回值来判断管道是否关闭?"
在Go语言中,我们是否可以使用读取管道时的第二个返回值来判断管道是否关闭? 可以看下面的代码
package main
import "fmt"
func main() {
// 创建一个整型管道
ch := make(chan int)
// 启动一个协程往管道发送数据
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
// 关闭管道
close(ch)
}()
// 能否判断管道是否关闭?
if _, ok := <-ch; !ok {
fmt.Println("管道已关闭")
}
}
在探讨这个问题之前,我们先来了解一下管道的数据结构,从go的源码,我们可以知道,管道是被定义为一个名为hchan的结构体:
type hchan struct {
qcount uint //当前队列中剩余的元素个数
dataqsiz uint // 环形队列管道容积
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 元素大小
closed uint32 // 标识管道关闭的状态
elemtype *_type // 元素类型
recvq waitq // 等待的读元素的协程队列
sendq waitq // 等待的写元素的协程队列
...
}
其中,有一个属性是我们应该关注的,那就是closed,这玩意标识了管道是否关闭,这玩意为1代表关闭了,为0代表是开启的.
好的,接下来我们继续本文探讨的问题在Go语言中,我们是否可以使用管道的第二个返回值来判断管道是否关闭?
先给出结论 : 从严格意义上来讲是不可以的,其实表示是否成功读取数据,但是在缓存区为0的时候,ok的状态和管道状态是一致的,所以会被误认为,这个ok是代表管道的状态
可以看下面的例子
package main
import (
"fmt"
"time"
)
func main() {
a2 := make(chan int, 2)
go demo(a2)
value2, ok2 := <-a2
fmt.Printf("value2:%v,ok2:%v\n", value2, ok2)
time.Sleep(3 * time.Second)
value3, ok3 := <-a2
fmt.Printf("value3:%v,ok3:%v\n", value3, ok3)
}
func demo(a chan int) {
defer func() {
close(a)
fmt.Println("管道已经关闭")
}()
a <- 1
a <- 2
}
’ 管道已经关闭 ’ 这是最先打印的,无论运行多少次,都是一样的,而且我还特地将main函数暂停了3秒,所以我可以保证demo函数已经执行完毕,demo协程已经销毁,然后再执行的第二个管道的数据的读取
其实是代表读取数据是否成功,或者说代表缓存区是否还有数据
首先我们要知道, 关闭了的管道, 我们还是可以进行读取的, 这个设定是因为有缓存的存在, 但是如果管道关闭了的话,又没有值,读取的话,会是类型的默认值和false,也就是读取未成功
当然如果是缓存区为0的情况,ok的值和管道的状态是一致的
var c = make(chan int)
close(c)
value, ok := <-c
fmt.Printf("value:%v \nok:%v \n", value, ok)