Forgive a bit of ignorance, it's been a bit since I've touched Go, but this looks awfully similar to a Java CountdownLatch [1]. Is this just a glorified Go port of that or am I missing something vital here?
They basically don't backport anything for Go, but the quid pro quo for that is that the backwards compatibility is pretty strong so upgrades should be safe. I have seen one serious issue from it, but still it's the language I'm the most confident to do an upgrade and expect things to Just Work afterwards.
I like WaitGroup as a concept, but I often end up using a channel instead for clearer error handling. Something like:
errCh := make(chan error)
for _, url := range urls {
go func(url string){
errCh <- http.Get(url)
}(url)
}
for range urls {
err := <-errCh
if err != nil {
// handle error
}
}
Should I be using WaitGroup instead? If I do, don't I still need an error channel anyway—in which case it feels redundant? Or am I thinking about this wrong? I rarely encounter concurrency situations that the above pattern doesn't seem sufficient for.
Love this. Majority of concurrency in a usual web service is implemented using waitgroups IME (see below) This will greatly simplify it.
var wg sync.WaitGroup
wg.Add(1)
go func(){
callService1(inputs, outParameter)
wg.Done()
}
// Repeat for services 2 through N
wg.Wait()
// Combine all outputs
BTW, this can already be done with a wrapper type
type WaitGroup struct { sync.WaitGroup }
func (wg *WaitGroup) Go(fn func()) {
wg.Add(1)
go func() {
fn()
wg.Done()
}()
}
Since you're doing struct embedding you can call methods of sync.WaitGroup on the new WaitGroup type as well.
multiple ways of doing something isn't inherently bad.
For example, if you want to set a variable to the number of seconds in seven hours, you could just set the variable to 25200, or you could set it to 60 * 60 * 7. The expanded version might be clearer in the code context, but in the end they do exactly the same thing.
zero value. container-agnostic initialization. say your type is not struct anymore, you would not have to change the way you intialize it. what you care here is zero value, and let the type figure out that it is zero and use methods appropriately. and it is just more clean this way
This means you can't pass variables in as function arguments. Even the example in the official go docs doesn't handle the scope correctly:
func main() {
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.example.com/",
}
for _, url := range urls {
// Launch a goroutine to fetch the URL.
wg.Go(func() {
// Fetch the URL.
http.Get(url)
})
}
// Wait for all HTTP fetches to complete.
wg.Wait()
}
[1] https://docs.oracle.com/javase/8/docs/api/java/util/concurre...
[0] https://docs.oracle.com/javase/7/docs/api/java/util/concurre...
What is quite sad is that we cannot add it ourselves as it's so simple of what they have done:
errgroup also has other niceties like error propagation, context cancellation, and concurrency limiting.
But channels already do the waiting part for you.
just `var wg sync.WaitGroup`, it is cleaner this way
For example, if you want to set a variable to the number of seconds in seven hours, you could just set the variable to 25200, or you could set it to 60 * 60 * 7. The expanded version might be clearer in the code context, but in the end they do exactly the same thing.
cool it down a little. touch some grass. and hopefully you will see beauty in Go zero-values :P
here is google guideline: https://google.github.io/styleguide/go/best-practices#declar...
You need to use this pattern instead:
https://go.dev/blog/loopvar-preview
Well, you could...
> You need to use this pattern insteadWhy? Seems rather redundant. It is not like WaitGroup.Go exists in earlier versions.