How to check a channel is closed or not without reading it?

前端 未结 8 839
别跟我提以往
别跟我提以往 2020-12-12 14:56

This is a good example of workers & controller mode in Go written by @Jimt, in answer to \"Is there some elegant way to pause & resume any other goroutine in golang?

相关标签:
8条回答
  • 2020-12-12 15:10

    You could set your channel to nil in addition to closing it. That way you can check if it is nil.

    example in the playground: https://play.golang.org/p/v0f3d4DisCz

    edit: This is actually a bad solution as demonstrated in the next example, because setting the channel to nil in a function would break it: https://play.golang.org/p/YVE2-LV9TOp

    0 讨论(0)
  • 2020-12-12 15:16

    In a hacky way it can be done for channels which one attempts to write to by recovering the raised panic. But you cannot check if a read channel is closed without reading from it.

    Either you will

    • eventually read the "true" value from it (v <- c)
    • read the "true" value and 'not closed' indicator (v, ok <- c)
    • read a zero value and the 'closed' indicator (v, ok <- c)
    • will block in the channel read forever (v <- c)

    Only the last one technically doesn't read from the channel, but that's of little use.

    0 讨论(0)
  • 2020-12-12 15:19

    it's easier to check first if the channel has elements, that would ensure the channel is alive.

    func isChanClosed(ch chan interface{}) bool {
        if len(ch) == 0 {
            select {
            case _, ok := <-ch:
                return !ok
            }
        }
        return false 
    }
    
    0 讨论(0)
  • 2020-12-12 15:21

    There's no way to write a safe application where you need to know whether a channel is open without interacting with it.

    The best way to do what you're wanting to do is with two channels -- one for the work and one to indicate a desire to change state (as well as the completion of that state change if that's important).

    Channels are cheap. Complex design overloading semantics isn't.

    [also]

    <-time.After(1e9)
    

    is a really confusing and non-obvious way to write

    time.Sleep(time.Second)
    

    Keep things simple and everyone (including you) can understand them.

    0 讨论(0)
  • 2020-12-12 15:30

    Well, you can use default branch to detect it, for a closed channel will be selected, for example: the following code will select default, channel, channel, the first select is not blocked.

    func main() {
        ch := make(chan int)
    
        go func() {
            select {
            case <-ch:
                log.Printf("1.channel")
            default:
                log.Printf("1.default")
            }
            select {
            case <-ch:
                log.Printf("2.channel")
            }
            close(ch)
            select {
            case <-ch:
                log.Printf("3.channel")
            default:
                log.Printf("3.default")
            }
        }()
        time.Sleep(time.Second)
        ch <- 1
        time.Sleep(time.Second)
    }
    

    Prints

    2018/05/24 08:00:00 1.default
    2018/05/24 08:00:01 2.channel
    2018/05/24 08:00:01 3.channel
    
    0 讨论(0)
  • 2020-12-12 15:33

    I know this answer is so late, I have wrote this solution, Hacking Go run-time, It's not safety, It may crashes:

    import (
        "unsafe"
        "reflect"
    )
    
    
    func isChanClosed(ch interface{}) bool {
        if reflect.TypeOf(ch).Kind() != reflect.Chan {
            panic("only channels!")
        }
    
        // get interface value pointer, from cgo_export 
        // typedef struct { void *t; void *v; } GoInterface;
        // then get channel real pointer
        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
    }
    

    https://gist.github.com/youssifsayed/ca0cfcf9dc87905d37a4fee7beb253c2

    0 讨论(0)
提交回复
热议问题