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

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

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: Stopped\n", id)
                return
            case Running:
                fmt.Printf("Worker %d: Running\n", id)
            case Paused:
                fmt.Printf("Worker %d: Paused\n", 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: Восстановление поднятой паники - это то, что я пробовал, но она закроет горутин, который вызвал панику. В этом случае он будет контроллером, поэтому он не будет использоваться.

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

Ответ 1

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

Либо вы

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

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

Ответ 2

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

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

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

[также]

<-time.After(1e9)

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

time.Sleep(time.Second)

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

Ну, вы можете использовать ветвь по 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)
}

Ответ 7

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

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

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

Ответ 8

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

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