web-dev-qa-db-fra.com

Comment vérifier qu'un canal est fermé ou non sans l'avoir lu?

Ceci est un bon exemple du mode workers & controller en Go écrit par @Jimt, en réponse à " Existe-t-il un moyen élégant de mettre en pause et de reprendre un autre goroutine en 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: 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])
    }
}

Mais ce code a aussi un problème: si vous voulez supprimer un canal de travail dans workers lorsque worker() se ferme, un verrouillage mort se produit.

Si vous close(workers[i]), la prochaine fois que le contrôleur l'écrira provoquera une panique, car go ne pourra pas écrire dans un canal fermé. Si vous utilisez un mutex pour le protéger, il sera bloqué sur workers[i] <- Running Car le worker ne lit rien dans le canal et l'écriture sera bloquée, et mutex provoquera un blocage total. Vous pouvez également donner un tampon plus important à canaliser comme solution de contournement, mais cela ne suffit pas.

Donc, je pense que le meilleur moyen de résoudre ce problème est de worker() fermer le canal à la sortie. Si le contrôleur trouve un canal fermé, il sautera dessus et ne fera rien. Mais je ne trouve pas comment vérifier si une chaîne est déjà fermée ou non dans cette situation. Si j'essaie de lire le canal dans le contrôleur, le contrôleur peut être bloqué. Donc, je suis très confus pour l'instant.

PS: Ce que j’ai essayé, c’est de récupérer de la panique surélevée, mais cela fermera la gorge qui a provoqué la panique. Dans ce cas, ce sera un contrôleur, donc ce n’est pas utile.

Néanmoins, je pense qu'il est utile que l'équipe Go implémente cette fonction dans la prochaine version de Go.

56
Reck Hou

De manière simpliste, cela peut être fait pour les canaux auxquels on tente d’écrire en récupérant la panique qui s’éveille. Mais vous ne pouvez pas vérifier si un canal de lecture est fermé sans le lire.

Soit vous

  • éventuellement lu la "vraie" valeur (v <- c)
  • lire la valeur "true" et l'indicateur "non fermé" (v, ok <- c)
  • lire une valeur nulle et l'indicateur 'fermé' (v, ok <- c)
  • bloquera dans le canal lu pour toujours (v <- c)

Techniquement, seul le dernier ne lit pas le canal, mais cela ne sert à rien.

45
zzzz

Il n'y a aucun moyen d'écrire une application sécurisée où vous devez savoir si un canal est ouvert sans interagir avec lui.

La meilleure façon de faire ce que vous voulez faire est d'utiliser deux canaux: un pour le travail et un pour indiquer votre désir de changer d'état (ainsi que l'achèvement de ce changement d'état si c'est important).

Les chaînes sont bon marché. La sémantique de surcharge de conception complexe ne l'est pas.

[également]

<-time.After(1e9)

est un moyen vraiment déroutant et non évident d'écrire

time.Sleep(time.Second)

Gardez les choses simples et tout le monde (vous compris) peut les comprendre.

64
Dustin

Je sais que cette réponse est si tardive que j'ai écrit cette solution, Piratage Go-run-time , Ce n'est pas la sécurité, Cela peut se bloquer:

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

De la documentation:

Un canal peut être fermé avec la fonction intégrée close. Le formulaire d'affectation à valeurs multiples de l'opérateur de réception indique si une valeur reçue a été envoyée avant la fermeture du canal.

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

L'exemple de Golang en action montre ce cas:

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

Eh bien, vous pouvez utiliser default branche pour le détecter, pour un canal fermé sera sélectionné, par exemple: le code suivant sélectionnera default, channel, channel, la première sélection n'est pas bloquée.

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

Peut-être qu'il me manque quelque chose, mais il semble que la façon simple et correcte de gérer cela consiste à envoyer "arrêté" au canal (qui termine la routine aller), à fermer le canal et à le mettre à zéro.

Si vous pensez avoir besoin de rechercher un canal fermé sans le lire, il y a un problème avec votre conception. (Notez qu'il existe d'autres problèmes avec le code, tels que la "mise en boucle occupée" des travailleurs mis en veille.)

0