前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中判断管道是否关闭的误区

Go语言中判断管道是否关闭的误区

原创
作者头像
言志志
发布2023-10-11 11:35:39
4740
发布2023-10-11 11:35:39
举报
文章被收录于专栏:Go语言学习笔记

前言

本文是探讨的是"在Go语言中,我们是否可以使用读取管道时的第二个返回值来判断管道是否关闭?"

此文章是个人学习归纳的心得,未经允许,严禁转载, 如有不对, 还望斧正, 感谢!

样例

在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的结构体:

代码语言:go
复制
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是代表管道的状态

可以看下面的例子

代码语言:txt
复制
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

}

解释一下运行流程

  • 首先创建了一个缓存区为2的管道a2
  • 然后用go关键字 将demo函数开辟出一个新的协程运行,此时demo和main是同一级的关系,同时运行,此时main函数会继续向下执行,发现是从管道中读取一个元素,然后就会等待demo函数会向管道中传入值,demo函数的运行过程是这样的,它发现管道a2的缓存是2,所以刚好把元素存入,然后就执行关闭管道,然后demo协程销毁
  • main函数继续执行,接收到a2管道的一个元素之后,然后返回value2和ok2,然后进行打印
  • 然后休眠3秒钟
  • 然后继续读取a2管道的元素,得到value3和ok3,然后打印

打印结果:

ok2和ok3都为true

' 管道已经关闭 ' 这是最先打印的,无论运行多少次,都是一样的,而且我还特地将main函数暂停了3秒,所以我可以保证demo函数已经执行完毕,demo协程已经销毁,然后再执行的第二个管道的数据的读取

逐步调试

  • 那我们调试一下,可以发现,执行了make函数创建管道之后,管道没有关闭,我前面特意提了管道的数据结构,其中closed是标识管道是否关闭的
  • 继续调试,我们可以发现,在读取完管道a2的第一个值赋值给value2和ok2的时候,此时通道已经关闭
  • value2的值为1,ok2为true
  • 继续调试,通道还是关闭状态,但是ok3的值还是true,看下面的第二张图

所以读取管道元素传来的第二个值,并不是代表管道是否关闭!

那它代表什么?

其实是代表读取数据是否成功,或者说代表缓存区是否还有数据

首先我们要知道, 关闭了的管道, 我们还是可以进行读取的, 这个设定是因为有缓存的存在, 但是如果管道关闭了的话,又没有值,读取的话,会是类型的默认值和false,也就是读取未成功

当然如果是缓存区为0的情况,ok的值和管道的状态是一致的

代码语言:go
复制
var c = make(chan int)

close(c)

value, ok := <-c

fmt.Printf("value:%v \nok:%v \n", value, ok)

运行结果:

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 样例
  • 探讨
    • 管道的数据结构
      • 详细分析
        • 解释一下运行流程
        • 逐步调试
        • 那它代表什么?
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档