Home > database >  Go concurrency, goroutine synchronization and closing channels
Go concurrency, goroutine synchronization and closing channels

Time:08-19

Familiarizing myself with concurrency, so started writing a simple ping cli with concurrent calls (let's ignore that I'm not really measuring pings).

Problem is, I can't close the channel properly for the range loop while also waiting for all the goroutines to finish. If I want to concurrently call the ping function, I can't synchronize it with waitgroups, because I will reach the wg.Wait() line before all the goroutines finish.

Is there a way to keep the ping calls concurrent and close the channel after they're done, so the range loop can exit?

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

CodePudding user response:

You must not copy a sync.WaitGroup! It's doc explicitly states:

A WaitGroup must not be copied after first use.

Pass a pointer to it: wg *sync.WaitGroup. And call wg.Done() deferred! You have other return statements which will cause wg.Done() to be skipped!

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

Today's IDE's (and golinter) warns about such obvious misuses. To avoid such mistakes, declare wg to be a pointer in the first place:

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

This leaves less room for errors and misuses.

  • Related