it-swarm.com.de

Wie überprüfe ich, ob ein Kanal geschlossen ist oder nicht, ohne ihn zu lesen?

Dies ist ein gutes Beispiel für den Worker & Controller-Modus in Go, geschrieben von @Jimt, als Antwort auf " Gibt es eine elegante Möglichkeit, eine andere Goroutine in Golang anzuhalten und fortzusetzen? "

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

Dieser Code hat jedoch auch ein Problem: Wenn Sie einen Arbeitskanal in workers entfernen möchten, wenn worker() beendet wird, wird die Deadlock-Funktion ausgeführt.

Wenn Sie close(workers[i]) das nächste Mal schreiben, kommt es zu einer Panik, da go nicht in einen geschlossenen Kanal schreiben kann. Wenn Sie einen Mutex verwenden, um ihn zu schützen, bleibt er auf workers[i] <- Running Hängen, da der worker nichts aus dem Kanal liest und das Schreiben blockiert wird, und der Mutex führt zu einem Dead Lock. Sie können dem Channel auch einen größeren Puffer als Workaround zuweisen, der jedoch nicht ausreicht.

Ich denke, der beste Weg, dies zu lösen, ist worker() close channel when exit. Wenn der Controller feststellt, dass ein Kanal geschlossen ist, wird er darüber springen und nichts tun. Ich kann jedoch nicht feststellen, ob ein Kanal in dieser Situation bereits geschlossen ist oder nicht. Wenn ich versuche, den Kanal im Controller zu lesen, ist der Controller möglicherweise blockiert. Ich bin also im Moment sehr verwirrt.

PS: Ich habe versucht, die ausgelöste Panik wieder herzustellen, aber es wird Goroutine schließen, die die ausgelöste Panik auslöst. In diesem Fall wird es Controller sein, so dass es keinen Zweck hat.

Trotzdem halte ich es für nützlich, dass das Go-Team diese Funktion in der nächsten Version von Go implementiert.

56
Reck Hou

Auf eine hackige Weise kann es für Kanäle gemacht werden, auf die man zu schreiben versucht, indem man die ausgelöste Panik wiederherstellt. Sie können jedoch nicht überprüfen, ob ein Lesekanal geschlossen ist, ohne von ihm zu lesen.

Entweder du wirst

  • lies irgendwann den "wahren" Wert daraus (v <- c)
  • lesen Sie den Wert "true" und den Indikator "not closed" (v, ok <- c)
  • lesen Sie einen Nullwert und die Anzeige 'geschlossen' (v, ok <- c)
  • blockiert den Kanal für immer gelesen (v <- c)

Nur der letzte liest technisch nicht aus dem Kanal, aber das nützt wenig.

45
zzzz

Es gibt keine Möglichkeit, eine sichere Anwendung zu schreiben, bei der Sie wissen müssen, ob ein Kanal geöffnet ist, ohne mit ihm zu interagieren.

Der beste Weg, um das zu tun, was Sie wollen, sind zwei Kanäle - einer für die Arbeit und einer, um den Wunsch anzuzeigen, den Status zu ändern (sowie den Abschluss dieser Statusänderung, wenn dies wichtig ist).

Kanäle sind billig. Komplexes Design überladen Semantik ist nicht.

[ebenfalls]

<-time.After(1e9)

ist eine wirklich verwirrende und nicht offensichtliche Art zu schreiben

time.Sleep(time.Second)

Halten Sie die Dinge einfach und jeder (einschließlich Sie) kann sie verstehen.

64
Dustin

Ich weiß, diese Antwort ist so spät, ich habe diese Lösung geschrieben, Hacking Go run-time , Es ist keine Sicherheit, es kann abstürzen:

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

5
youssif

Aus der Dokumentation:

Ein Kanal kann mit der eingebauten Funktion close geschlossen werden. Das mehrwertige Zuweisungsformular des Empfangsoperators gibt an, ob ein empfangener Wert gesendet wurde, bevor der Kanal geschlossen wurde.

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

Ein Beispiel von Golang in Aktion zeigt diesen Fall:

// 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
    }
}
0
Israel Barba

Nun, Sie können den Zweig default verwenden, um ihn zu erkennen, da beispielsweise ein geschlossener Kanal ausgewählt wird: Der folgende Code wählt default, channel, channel, die erste Auswahl ist nicht blockiert.

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

Vielleicht fehlt mir etwas, aber es scheint, dass die einfache und korrekte Möglichkeit, dies zu tun, darin besteht, "gestoppt" an den Kanal zu senden (was die Go-Routine beendet), den Kanal zu schließen und ihn auf null zu setzen.

Wenn Sie der Meinung sind, dass Sie nach einem geschlossenen Kanal suchen müssen, ohne ihn zu lesen, liegt ein Problem mit Ihrem Design vor. (Beachten Sie, dass es andere Probleme mit dem Code gibt, z. B. das "Besetztschleifen" von angehaltenen Arbeitnehmern.)

0