Is this example usage of sync.WaitGroup
correct? It gives the expected result, but I am unsure about the wg.Add(4)
and the position of wg.Done()
. Does it make sense to add the four goroutines at once with wg.Add()
?
http://play.golang.org/p/ecvYHiie0P
package main
import (
"fmt"
"sync"
"time"
)
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(4)
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
Result (as expected):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
sync. WaitGroup provides a goroutine synchronization mechanism in Golang , and is used for waiting for a collection of goroutines to finish.
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then, each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
Also, channels use sync underneath thus using sync should be more performant. WaitGroup helps when you have to block wait for many goroutines to return. It's simpler when you might spawn a hundred of them in a for loop.
In order to be able to run the goroutines until the finish, we can either make use of a channel that will act as a blocker or we can use waitGroups that Go's sync package provides us with.
Yes, this example is correct. It is important that the wg.Add()
happens before the go
statement to prevent race conditions. The following would also be correct:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
However, it is rather pointless to call wg.Add
over and over again when you already know how many times it will be called.
Waitgroups
panic if the counter falls below zero. The counter starts at zero, each Done()
is a -1
and each Add()
depends on the parameter. So, to ensure that the counter never drops below and avoid panics, you need the Add()
to be guaranteed to come before the Done()
.
In Go, such guarantees are given by the memory model.
The memory model states that all statements in a single goroutine appear to be executed in the same order as they are written. It is possible that they won't actually be in that order, but the outcome will be as if it was. It is also guaranteed that a goroutine doesn't run until after the go
statement that calls it. Since the Add()
occurs before the go
statement and the go
statement occurs before the Done()
, we know the Add()
occurs before the Done()
.
If you were to have the go
statement come before the Add()
, the program may operate correctly. However, it would be a race condition because it would not be guaranteed.
I would recommend embeding the wg.Add()
call into the doSomething()
function itself, so that if you adjust the number of times it's called, you don't have to separately adjust the add parameter manually which could lead to an error if you update one but forget to update the other (in this trivial example that is unlikely, but still, I personally believe it to be better practice for code re-use).
As Stephen Weinberg points out in his answer to this question, you do have to increment the waitgroup prior to spawning the gofunc, but you can accomplish this easily by wrapping the gofunc spawn inside the doSomething()
function itself, like this:
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}()
}
Then you can call it without the go
invocation, e.g.:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
As a playground: http://play.golang.org/p/WZcprjpHa_
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Done() duration := millisecs * time.Millisecond time.Sleep(duration) fmt.Println("Function in background, duration:", duration) }() } func main() { var wg sync.WaitGroup dosomething(200, &wg) dosomething(400, &wg) dosomething(150, &wg) dosomething(600, &wg) wg.Wait() fmt.Println("Done") }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With