• 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("管道已关闭")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    探讨 管道的数据结构

    在探讨这个问题之前,我们先来了解一下管道的数据结构,从go的源码,我们可以知道,管道是被定义为一个名为hchan的结构体

    type hchan struct {
        qcount   uint           //当前队列中剩余的元素个数
        dataqsiz uint           // 环形队列管道容积
        buf      unsafe.Pointer // 环形队列指针
        elemsize uint16         // 元素大小
        closed   uint32         // 标识管道关闭的状态
        elemtype *_type         // 元素类型
        recvq    waitq          // 等待的读元素的协程队列
        sendq    waitq          // 等待的写元素的协程队列
        ... 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其中,有一个属性是我们应该关注的,那就是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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    解释一下运行流程

    • 首先创建了一个缓存区为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的值和管道的状态是一致的

    var c = make(chan int)
    close(c)
    value, ok := <-c
    fmt.Printf("value:%v \nok:%v \n", value, ok)
    
    • 1
    • 2
    • 3
    • 4

    运行结果:
    在这里插入图片描述

  • 相关阅读:
    【leetcode】三数之和
    商务呈现之危机公关处理
    数组16—flat() :递归地将数组展平到指定的深度
    谷歌浏览器--关闭自动更新的方法
    如何判断自己遇到的攻击是SQL注入攻击
    (免费分享)基于ssm在线点餐
    排序算法(Java版)
    visual studio2019怎么修改字体
    2024年CSP-J暑假冲刺训练营(1):枚举
    【漏洞复现】短视频矩阵营销系统 ajax_uplaod接口处存在任意文件上传
  • 原文地址:https://blog.csdn.net/m0_73728511/article/details/133635822