Как проверить канал закрыт или нет не читая?

это хороший пример режима рабочих и контроллера в Go, написанном @Jimt, в ответ на "есть какой-то элегантный способ, чтобы сделать паузу и возобновить любая другая горутина в golang?"

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

// Possible worker states.
const (
    Stopped = 0
    Paused  = 1
    Running = 2
)

// Maximum number of workers.
const WorkerCount = 1000

func main() {
    // Launch workers.
    var wg sync.WaitGroup
    wg.Add(WorkerCount + 1)

    workers := make([]chan int, WorkerCount)
    for i := range workers {
        workers[i] = make(chan int)

        go func(i int) {
            worker(i, workers[i])
            wg.Done()
        }(i)
    }

    // Launch controller routine.
    go func() {
        controller(workers)
        wg.Done()
    }()

    // Wait for all goroutines to finish.
    wg.Wait()
}

func worker(id int, ws <-chan int) {
    state := Paused // Begin in the paused state.

    for {
        select {
        case state = <-ws:
            switch state {
            case Stopped:
                fmt.Printf("Worker %d: Stoppedn", id)
                return
            case Running:
                fmt.Printf("Worker %d: Runningn", id)
            case Paused:
                fmt.Printf("Worker %d: Pausedn", id)
            }

        default:
            // We use runtime.Gosched() to prevent a deadlock in this case.
            // It will not be needed of work is performed here which yields
            // to the scheduler.
            runtime.Gosched()

            if state == Paused {
                break
            }

            // Do actual work here.
        }
    }
}

// controller handles the current state of all workers. They can be
// instructed to be either running, paused or stopped entirely.
func controller(workers []chan int) {
    // Start workers
    for i := range workers {
        workers[i] <- Running
    }

    // Pause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Paused
    }

    // Unpause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Running
    }

    // Shutdown workers.
    <-time.After(1e9)
    for i := range workers {
        close(workers[i])
    }
}

но этот код также имеет проблему: если вы хотите удалить канал рабочего в workers, когда worker() выходит, тупик происходит.

если вы close(workers[i]), в следующий раз контроллер пишет в него вызовет панику, так как go не может писать в закрытый канал. Если вы используете некоторые мьютекс, чтобы защитить его, то он будет застрял на workers[i] <- Running С worker не читает ничего из канала, и запись будет заблокирована, а мьютекс вызовет мертвую блокировку. Вы также можете дать больший буфер для канала в качестве обходного пути, но этого недостаточно.

поэтому я думаю, что лучший способ решить это worker() закрыть канал при выходе, если контроллер обнаружит канал закрытым, он перепрыгнет через него и ничего не сделает. Но я не могу найти, Как проверить, что канал уже закрыт или не в этой ситуации. Если я попытаюсь прочитать канал в контроллере, контроллер может быть заблокирован. Так что сейчас я в замешательстве.

PS: восстановление поднятой паники - это то, что я пробовал, но он закроет goroutine, который поднял панику. В этом случае это будет контроллер, поэтому это бесполезно.

тем не менее, я думаю, что для Go team полезно реализовать эту функцию в следующей версии Go.

8 ответов


хакерским способом это можно сделать для каналов, на которые пытаются писать, восстанавливая поднятую панику. Но вы не можете проверить, закрыт ли канал чтения без чтения из него.

либо

  • в конечном итоге прочитайте значение " true "из него (v <- c)
  • прочитайте значение" true "и индикатор " not closed" (v, ok <- c)
  • считайте нулевое значение и индикатор' closed' (v, ok <- c)
  • будет блокировать в канале чтения навсегда (v <- c)

только последний технически не читает с канала, но это мало пользы.


нет способа написать безопасное приложение, в котором вам нужно знать, открыт ли канал без взаимодействия с ним.

лучший способ сделать то, что вы хотите сделать, - это два канала: один для работы, а другой для указания желания изменить состояние (а также завершения этого изменения состояния, если это важно).

каналы дешевые. Сложная конструкция перегружает семантику.

[также]

<-time.After(1e9)

- это правда запутанный и неочевидный способ написать

time.Sleep(time.Second)

держите вещи простыми, и каждый (включая вас) может их понять.


Я знаю, что этот ответ так поздно, я написал это решение, взлом бежать-времени, это не безопасность, это может произойти сбой:

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


может быть, мне чего-то не хватает, но кажется, что простой и правильный способ справиться с этим-отправить "остановленный" канал (который завершает GO-routine), закрыть канал и установить его на ноль.

Если вы думаете, что вам нужно проверить закрытый канал, не читая его, то есть проблема с вашим дизайном. (Обратите внимание, что есть и другие проблемы с кодом, такие как "цикл занятости" приостановленных работников.)


из документации:

канал может быть закрыт с помощью встроенной функции закрытия. Многозначная форма назначения оператора получения сообщает, Было ли передано полученное значение до закрытия канала.

https://golang.org/ref/spec#Receive_operator

пример Golang в действии показывает этот случай:

// This sample program demonstrates how to use an unbuffered
// channel to simulate a game of tennis between two goroutines.
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

// wg is used to wait for the program to finish.
var wg sync.WaitGroup

func init() {
    rand.Seed(time.Now().UnixNano())
}

// main is the entry point for all Go programs.
func main() {
    // Create an unbuffered channel.
    court := make(chan int)
    // Add a count of two, one for each goroutine.
    wg.Add(2)
    // Launch two players.
    go player("Nadal", court)
    go player("Djokovic", court)
    // Start the set.
    court <- 1
    // Wait for the game to finish.
    wg.Wait()
}

// player simulates a person playing the game of tennis.
func player(name string, court chan int) {
    // Schedule the call to Done to tell main we are done.
    defer wg.Done()
    for {
        // Wait for the ball to be hit back to us.
        ball, ok := <-court
        fmt.Printf("ok %t\n", ok)
        if !ok {
            // If the channel was closed we won.
            fmt.Printf("Player %s Won\n", name)
            return
        }
        // Pick a random number and see if we miss the ball.
        n := rand.Intn(100)
        if n%13 == 0 {
            fmt.Printf("Player %s Missed\n", name)
            // Close the channel to signal we lost.
            close(court)
            return
        }

        // Display and then increment the hit count by one.
        fmt.Printf("Player %s Hit %d\n", name, ball)
        ball++
        // Hit the ball back to the opposing player.
        court <- ball
    }
}

Ну, вы можете использовать default ветвь для ее обнаружения, для закрытого канала будет выбран, например: следующий код выберет default, channel, channel, первый выбор не блокируется.

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)
}

Если вы слушаете этот канал вы всегда сможете узнать, что канал был закрыт.

case state, opened := <-ws:
    if !opened {
         // channel was closed 
         // return or made some final work
    }
    switch state {
        case Stopped:

но помните, вы не можете закрыть один канал в два раза. Это вызовет панику.


проще сначала проверить, есть ли у канала элементы, которые гарантировали бы, что канал жив.

func isChanClosed(ch chan interface{}) bool {
    if len(ch) == 0 {
        select {
        case _, ok := <-ch:
            return !ok
        }
    }
    return false 
}