104 votes

Comment vérifier qu'un canal est fermé ou non sans le lire ?

Voici un bon exemple de mode workers & controller en Go écrit par @Jimt, en réponse à la question suivante " Existe-t-il un moyen élégant de mettre en pause et de reprendre n'importe quelle autre goroutine dans 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 travailleur dans workers cuando worker() sort, un verrou mort se produit.

Si usted close(workers[i]) La prochaine fois qu'un contrôleur y écrit, cela provoque une panique car il ne peut pas écrire dans un canal fermé. Si vous utilisez un mutex pour le protéger, alors il sera bloqué sur workers[i] <- Running depuis le worker ne lit rien depuis le canal et l'écriture sera bloquée, et le mutex causera un verrou mort. Vous pouvez aussi donner un plus grand tampon au canal comme solution de contournement, mais ce n'est pas suffisant.

Donc je pense que la meilleure façon de résoudre ce problème est de worker() fermer le canal à la sortie, si le contrôleur trouve un canal fermé, il sautera par-dessus et ne fera rien. Mais je ne trouve pas comment vérifier si un canal est déjà fermé ou non dans cette situation. Si j'essaie de lire le canal dans le contrôleur, le contrôleur pourrait être bloqué. Je suis donc très confus pour le moment.

PS : J'ai essayé de récupérer la panique soulevée, mais cela fermera la goroutine qui a soulevé la panique. Dans ce cas, il s'agira d'un contrôleur, donc ce n'est pas utile.

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

1voto

Vous pouvez mettre votre canal à zéro en plus de le fermer. De cette façon, vous pouvez vérifier si elle est nulle.

exemple dans la cour de récréation : https://play.golang.org/p/v0f3d4DisCz

éditer : C'est en fait une mauvaise solution comme le montre l'exemple suivant, parce que mettre le canal à nil dans une fonction le casserait : https://play.golang.org/p/YVE2-LV9TOp

0voto

Petrucho Peres Points 1
ch1 := make(chan int)
ch2 := make(chan int)
go func(){
    for i:=0; i<10; i++{
        ch1 <- i
    }
    close(ch1)
}()
go func(){
    for i:=10; i<15; i++{
        ch2 <- i
    }
    close(ch2)
}()
ok1, ok2 := false, false
v := 0
for{
    ok1, ok2 = true, true
    select{
        case v,ok1 = <-ch1:
        if ok1 {fmt.Println(v)}
        default:
    }
    select{
        case v,ok2 = <-ch2:
        if ok2 {fmt.Println(v)}
        default:
    }
    if !ok1 && !ok2{return}

}

}

-1voto

Israel Barba Points 255

Dans la documentation :

Un canal peut être fermé avec la fonction intégrée close. La forme d'affectation multivaluée 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 in 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
    }
}

-6voto

Enric Points 3

Il est plus facile de vérifier d'abord si le canal a des éléments, cela garantirait que le canal est vivant.

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

-8voto

jurka Points 5050

Si vous écoutez cette chaîne, vous pouvez toujours découvrir que la chaîne a été fermée.

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

Mais n'oubliez pas que vous ne pouvez pas fermer un canal deux fois. Cela provoquerait la panique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X