当前位置:   article > 正文

golang怎么判断chan已经关闭_go 判断chan是否关闭

go 判断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的关闭状态

  1. package main
  2. import (
  3.     "fmt"
  4. )
  5. func main() {
  6.     c := make(chan int10)
  7.     c <- 1
  8.     c <- 2
  9.     c <- 3
  10.     close(c)
  11.     for {
  12.         i,  ok := <-c
  13.         fmt.Println(ok)
  14.         if !ok {
  15.             fmt.Println("channel closed!")
  16.             break
  17.         }
  18.         fmt.Println(i)
  19.     }
  20. }

 

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

go runtime channel源码分析

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

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

  1. type hchan struct {
  2. qcount   uint           // total data in the queue
  3. dataqsiz uint           // size of the circular queue
  4. buf      unsafe.Pointer // points to an array of dataqsiz elements
  5. elemsize uint16
  6. closed   uint32
  7.         ...
  8. }

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

  1. //go:linkname reflect_chanclose reflect.chanclose
  2. func reflect_chanclose(c *hchan) {
  3. closechan(c)
  4. }
  5. func closechan(c *hchan) {
  6. if c == nil {
  7. panic(plainError("close of nil channel"))
  8. }
  9. lock(&c.lock)
  10. if c.closed != 0 {
  11. unlock(&c.lock)
  12. panic(plainError("close of closed channel"))
  13. }
  14.         ....
  15. c.closed = 1
  16.         ...
  17. }

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

  1. func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
  2.         ....
  3. lock(&c.lock)
  4. if c.closed != 0 && c.qcount == 0 {
  5. if raceenabled {
  6. raceacquire(unsafe.Pointer(c))
  7. }
  8. unlock(&c.lock)
  9. if ep != nil {
  10. typedmemclr(c.elemtype, ep)
  11. }
  12. return truefalse
  13. }
  14. if sg := c.sendq.dequeue(); sg != nil {
  15. recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
  16. return truetrue
  17. }
  18. if c.qcount > 0 {
  19. // Receive directly from queue
  20. qp := chanbuf(c, c.recvx)
  21. c.qcount--
  22. unlock(&c.lock)
  23. return truetrue
  24. }
  25.    ...
  26. }

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

解决方法

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

第一种方法:

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

  1. import (
  2.     "unsafe"
  3.     "reflect"
  4. )
  5. func isChanClosed(ch interface{}) bool {
  6.     if reflect.TypeOf(ch).Kind() != reflect.Chan {
  7.         panic("only channels!")
  8.     }
  9.     cptr := *(*uintptr)(unsafe.Pointer(
  10.         unsafe.Pointer(uintptr(unsafe.Pointer(&ch)) + unsafe.Sizeof(uint(0))),
  11.     ))
  12.     // this function will return true if chan.closed > 0
  13.     // see hchan on https://github.com/golang/go/blob/master/src/runtime/chan.go
  14.     // type hchan struct {
  15.     // qcount   uint           // total data in the queue
  16.     // dataqsiz uint           // size of the circular queue
  17.     // buf      unsafe.Pointer // points to an array of dataqsiz elements
  18.     // elemsize uint16
  19.     // closed   uint32
  20.     // **
  21.     cptr += unsafe.Sizeof(uint(0))*2
  22.     cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
  23.     cptr += unsafe.Sizeof(uint16(0))
  24.     return *(*uint32)(unsafe.Pointer(cptr)) > 0
  25. }

第二种方法:

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

  1.  ...
  2. ctx, cancel := context.WithCancel(context.Background())
  3. close(c)
  4. cancel()
  5. exit:
  6. for {
  7. select {
  8. case data, ok := <-c:
  9. fmt.Println(data, ok)
  10. case <-ctx.Done():
  11. break exit
  12. }
  13. }
  14.      ...

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/549128
推荐阅读
相关标签
  

闽ICP备14008679号