I'm working on a concurrent Go library, and I stumbled upon two distinct patterns of synchronization between goroutines whose results are similar:
Waitgroup
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { words := []string{"foo", "bar", "baz"} for _, word := range words { wg.Add(1) go func(word string) { time.Sleep(1 * time.Second) defer wg.Done() fmt.Println(word) }(word) } // do concurrent things here // blocks/waits for waitgroup wg.Wait() }
Channel
package main import ( "fmt" "time" ) func main() { words := []string{"foo", "bar", "baz"} done := make(chan bool) // defer close(done) for _, word := range words { // fmt.Println(len(done), cap(done)) go func(word string) { time.Sleep(1 * time.Second) fmt.Println(word) done <- true }(word) } // Do concurrent things here // This blocks and waits for signal from channel for range words { <-done } }
I was advised that sync.WaitGroup
is slightly more performant, and I have seen it being used commonly. However, I find channels more idiomatic. What is the real advantage of using sync.WaitGroup
over channels and/or what might be the situation when it is better?
A common usage of WaitGroup is to block the main function because we know that the main function itself is also a GoRoutine. You need to import the sync package before you can use WaitGroup.
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.
When a channel is created with no capacity, it is called an unbuffered channel. In turn, a channel created with capacity is called a buffered channel.
We can close a channel in Golang with the help of the close() function. Once a channel is closed, we can't send data to it, though we can still read data from it. A closed channel denotes a case where we want to show that the work has been done on this channel, and there's no need for it to be open.
Independently of the correctness of your second example (as explained in the comments, you aren't doing what you think, but it's easily fixable), I tend to think that the first example is easier to grasp.
Now, I wouldn't even say that channels are more idiomatic. Channels being a signature feature of the Go language shouldn't mean that it is idiomatic to use them whenever possible. What is idiomatic in Go is to use the simplest and easiest to understand solution: here, the WaitGroup
convey both the meaning (your main function is Wait
ing for workers to be done) and the mechanic (the workers notify when they are Done
).
Unless you're in a very specific case, I don't recommend using the channel solution here.
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