教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golang怎么判断chan已经关闭

golang怎么判断chan已经关闭

发布时间:2023-03-13   编辑:jiaochengji.com
教程集为您提供golang怎么判断chan已经关闭等资源,欢迎您收藏本站,我们将为您提供最新的golang怎么判断chan已经关闭资源

我们都知道data, ok := <- chan第一个变量表示读出的数据,第二个变量表示是否成功读取了数据,有意思的是,第二个变量并不用于指示管道的关闭的状态。第二个变量常常被误以为关闭状态是因为它确实和管道状态有关,确切的来说,是和管道缓冲区是否有数据有关。

如果判断golang的channel是否关闭,data, ok := <- chan,当ok不是true的时候,说明是channel关闭了。 那么问题来了,channel关闭了,我们是否可以立马获取到channel被关闭的状态?我想这个问题不少人没有去想吧?为什么有这样的问题?  来自我的一个bug,我期初认为close了一个channel,消费端的goroutine自然是可以拿到channel的关闭状态。然而事实并不是这样的。 只有当channel无数据,且channel被close了,才会返回ok=false。  所以,只要有堆积,就不会返回关闭状态。导致我的服务花时间来消费堆积,才会退出。

测试channel的关闭状态

package main



import (

    "fmt"

)



func main() {

    c := make(chan int, 10)

    c <- 1

    c <- 2

    c <- 3

    close(c)

    for {

        i,  ok := <-c

        fmt.Println(ok)

        if !ok {

            fmt.Println("channel closed!")

            break

        }

        fmt.Println(i)

    }

}

 

我们发现已经把channel关闭了,只要有堆积的数据,那么ok就不为false,不为关闭的状态。

go runtime channel源码分析

   首先我们来分析下go runtime/chan.go的相关源码,记得先前写过一篇golang channel实现的源码分析,有兴趣的朋友可以翻翻。 这次翻channel源码主要探究下close chan过程及怎么查看channel是否关闭?

   下面是channel的hchan主数据结构,closed字段就是标明是否退出的标识。

type hchan struct {

qcount   uint           // total data in the queue

dataqsiz uint           // size of the circular queue

buf      unsafe.Pointer // points to an array of dataqsiz elements

elemsize uint16

closed   uint32

        ...

}

下面是关闭channel的函数,修改了closed字段为1, 1为退出。

//go:linkname reflect_chanclose reflect.chanclose

func reflect_chanclose(c *hchan) {

closechan(c)

}



func closechan(c *hchan) {

if c == nil {

panic(plainError("close of nil channel"))

}



lock(&c.lock)

if c.closed != 0 {

unlock(&c.lock)

panic(plainError("close of closed channel"))

}

        ....

c.closed = 1

        ...

}

    下面是channel的recv消费者方法,也就是 data, ok := <- chan。if c.closed != 0 && c.qcount == 0 只有当 closed为1 并且 堆积为0的时候,才会返回false。 一句话,channel已经closed,并且没有堆积任务,才会返回关闭channel的状态。

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {

        ....

lock(&c.lock)



if c.closed != 0 && c.qcount == 0 {

if raceenabled {

raceacquire(unsafe.Pointer(c))

}

unlock(&c.lock)

if ep != nil {

typedmemclr(c.elemtype, ep)

}

return true, false

}



if sg := c.sendq.dequeue(); sg != nil {

recv(c, sg, ep, func() { unlock(&c.lock) }, 3)

return true, true

}



if c.qcount > 0 {

// Receive directly from queue

qp := chanbuf(c, c.recvx)

c.qcount--

unlock(&c.lock)

return true, true

}

   ...

}

channel代码里没有找到一个查询channel关闭的方法。

解决方法

那么如何在channel堆积的情况下,得知channel已经关闭了 ?

第一种方法:

可以直接读取channel结构hchan的closed字段,但问题chan.go没有开放这样的api,所以我们要用reflect这个黑科技了。  (不推荐大家用reflect的方法,因为看起来太黑科技了)

import (

    "unsafe"

    "reflect"

)



func isChanClosed(ch interface{}) bool {

    if reflect.TypeOf(ch).Kind() != reflect.Chan {

        panic("only channels!")

    }

    cptr := *(*uintptr)(unsafe.Pointer(

        unsafe.Pointer(uintptr(unsafe.Pointer(&ch))   unsafe.Sizeof(uint(0))),

    ))



    // this function will return true if chan.closed > 0

    // see hchan on https://github.com/golang/go/blob/master/src/runtime/chan.go

    // type hchan struct {

    // qcount   uint           // total data in the queue

    // dataqsiz uint           // size of the circular queue

    // buf      unsafe.Pointer // points to an array of dataqsiz elements

    // elemsize uint16

    // closed   uint32

    // **



    cptr  = unsafe.Sizeof(uint(0))*2

    cptr  = unsafe.Sizeof(unsafe.Pointer(uintptr(0)))

    cptr  = unsafe.Sizeof(uint16(0))

    return *(*uint32)(unsafe.Pointer(cptr)) > 0

}

第二种方法:

配合一个context或者一个变量来做。就拿context来说,那么select不仅可以读取数据chan,且同时监听<- context.Done() , 当context.Done()有事件,直接退出就ok了。

 ...

ctx, cancel := context.WithCancel(context.Background())

close(c)

cancel()

exit:

for {

select {

case data, ok := <-c:

fmt.Println(data, ok)



case <-ctx.Done():

break exit

}

}

     ...

 

到此这篇关于“golang怎么判断chan已经关闭”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang面试官:for select时,如果通道已经关闭会怎么样?如果select中只有一个case呢?
不要等离职了,才知道for select时,如果通道已经关闭会怎么样?
golang for循环_golang面试官:for select时,如果通道已经关闭会怎么样?如果select中只有一个case呢?...
android 读写串口中文字符串_golang面试题:对已经关闭的的chan进行读写,会怎么样?为什么?...
c builder case内是局部变量吗_golang面试官:for select时,如果通道已经关闭会怎么样?如果只有一个...
Go并发编程——channel
Go中使用channel控制goroutine退出
Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作
go语言管道总结
Go Chanel 使用与原理 一

[关闭]
~ ~