Different wg.Done() causes WaitGroup is reused before previous Wait has returned

Issue

I am testing sync.WaitGroup, if I put defer wg.Done() in the begining of the function, like this:

package main

import (
        "fmt"
        "sync"
        "time"
)

func worker(wg *sync.WaitGroup, id int) error {
    defer wg.Done() // put here cause error
    fmt.Printf("Worker %v: Finished\n", id)
    if true {
        return nil
    }

    return nil
}

var wg sync.WaitGroup // I should put `wg` outside of this function
func callWorker(i int){
    fmt.Println("Main: Starting worker", i)
    fmt.Printf("Worker %v: Finished\n", id)
    wg.Add(1)
    go worker(&wg, i)
    wg.Wait()
}

func main() {
    for i := 0; i < 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
    fmt.Println("Main: Waiting for workers to finish")

    fmt.Println("Main: Completed")
}

I will get WaitGroup is reused before previous Wait has returned in some cases, like this

enter image description here

but if I put defer wg.Done() in the end of function, it runs successfully, why?

func worker(wg *sync.WaitGroup, id int) error {
    
    fmt.Printf("Worker %v: Finished\n", id)
    if true {
        return nil
    }
    defer wg.Done() // put here, it is ok
    return nil
}

Solution

The docs state that "If a WaitGroup is reused to wait for several independent sets of events, new Add calls must happen after all previous Wait calls have returned"

You are calling wg.Done() in some goroutines before calling wg.Add(1) in others, which is not allowed, as stated by the docs. You need to call wg.Add before you start all those goroutines, and you might as well just call it once, wg.Add(1000)

The reason your other code works is that it never calls wg.Done(), you have

if true {
     return nil
}
defer wg.Done()

so you always return without reaching the defer statement, so there are never any calls to wg.Done().

Do this:

func callWorker(i int){
    fmt.Println("Main: Starting worker", i)
    // you cannot call Add here because Done has been called in other goroutines
    go worker(&wg, i)
    wg.Wait()
}

func main() {
    wg.Add(1000) // <---- You must call Add before Done is called in any goroutine
    for i := 0; i < 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
    fmt.Println("Main: Completed")
}

Answered By – Sean F

Answer Checked By – Terry (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.