2 votes

Concurrence Go, synchronisation goroutine et fermeture de canaux

Me familiarisant avec la concurrence, j'ai commencé à écrire un simple cli ping avec des appels concurrents (ignorons que je ne mesure pas vraiment les pings).

Le problème est que je ne peux pas fermer le canal correctement pour la boucle de portée tout en attendant que toutes les goroutines se terminent. Si je veux appeler simultanément la fonction ping, je ne peux pas la synchroniser avec les groupes d'attente, car j'atteindrai la ligne wg.Wait() avant la fin de toutes les goroutines.

Existe-t-il un moyen de maintenir les appels ping simultanés et de fermer le canal une fois qu'ils sont terminés, afin que la boucle de portée puisse se terminer ?

func main() {
    domain := flag.String("domain", "google.com", "the domain u want to ping")
    flag.Parse()

    sum := 0
    ch := make(chan int)

    go start_pings(*domain, ch)

    for elapsed := range ch {
        fmt.Println("Part time: " + strconv.Itoa(elapsed))
        sum += elapsed
    }

    avg := sum / 3
    fmt.Println("Average: " + strconv.Itoa(avg))
}

func start_pings(domain string, ch chan int) {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go ping(domain, ch, wg)
    }
    wg.Wait()
    close(ch)
}

func ping(domain string, ch chan int, wg sync.WaitGroup) {
    url := "http://" + domain
    start := time.Now()

    fmt.Println("Start pinging " + url + "...")

    resp, err := http.Get(url)
    elapsed := time.Now().Sub(start)

    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()

    ch <- int(elapsed)
    wg.Done()
}

3voto

icza Points 3857

Vous ne devez pas copier un sync.WaitGroup ! Son document stipule explicitement :

Un Groupe d'attente ne doit pas être copié après sa première utilisation.

Passez un pointeur sur lui : wg *sync.WaitGroup . Et appelez wg.Done() différé ! Vous avez d'autres return des déclarations qui causeront wg.Done() à sauter !

func start_pings(domain string, ch chan int) {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go ping(domain, ch, &wg)
    }
    wg.Wait()
    close(ch)
}

func ping(domain string, ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    url := "http://" + domain
    start := time.Now()

    fmt.Println("Start pinging " + url + "...")

    resp, err := http.Get(url)
    elapsed := time.Since(start)

    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()

    ch <- int(elapsed)
}

Les IDE d'aujourd'hui (et golinter) mettent en garde contre ces abus évidents. Pour éviter de telles erreurs, déclarez wg pour être un pointeur en premier lieu :

func start_pings(domain string, ch chan int) {
    wg := &sync.WaitGroup{} // Pointer!
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go ping(domain, ch, wg)
    }
    wg.Wait()
    close(ch)
}

Cela laisse moins de place aux erreurs et aux mauvais usages.

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