What is quite sad is that we cannot add it ourselves as it's so simple of what they have done:
func (wg *WaitGroup) Go(f func()) {
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
}
package main
import (
"sync"
"time"
)
type WaitGroup struct {
sync.WaitGroup
}
func (wg *WaitGroup) Go(fn func()) {
wg.Add(1)
go func() {
defer wg.Done()
fn()
}()
}
func main() {
var wg WaitGroup
wg.Go(func() { time.Sleep(1 * time.Second) })
wg.Wait()
}
errgroup also has other niceties like error propagation, context cancellation, and concurrency limiting.
At best, using the optional, higher-effort errgroup.WithContext will cancel the context but still run all of your funcs. If you don't want that for one of the funcs, or some component of them, just don't use the context.
You could also just make your subtask function return nil always, if you just want to get the automatic bookkeeping call pattern (like WaitGroup.Go from Golang 1.25), plus optional concurrency limiting.
Also note, even if a subtask function returns an error, the errgroup Wait blocking semantics are identical to those of a WaitGroup. Wait will return the first error when it returns, but it doesn't unblock early on first error.
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. 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()
}
https://pkg.go.dev/sync#example-WaitGroupYou need to use this pattern instead:
for _, url := range urls {
url := url
// ...
Well, you could...
for _, url := range urls {
wg.Go(func(u string) func() {
return func() {
http.Get(u)
}
}(url))
}
> You need to use this pattern insteadWhy? Seems rather redundant. It is not like WaitGroup.Go exists in earlier versions.
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. errCh := make(chan error, len(urls))
Right, but it prevents goroutine leaks. In these situations I'm usually fine with bailing on the first error, but I grant that's not always desirable. If it's not, I would collect and join errors and return those along with partial results (if those are useful).
But channels already do the waiting part for you.
[1] https://docs.oracle.com/javase/8/docs/api/java/util/concurre...
[0] https://docs.oracle.com/javase/7/docs/api/java/util/concurre...
nikolayasdf123•6h ago
just `var wg sync.WaitGroup`, it is cleaner this way
mr90210•5h ago
nikolayasdf123•5h ago
cool it down a little. touch some grass. and hopefully you will see beauty in Go zero-values :P
dwb•5h ago
nikolayasdf123•5h ago
here is google guideline: https://google.github.io/styleguide/go/best-practices#declar...
dwb•5h ago
fozdenn•5h ago
nikolayasdf123•5h ago
unsnap_biceps•3h ago
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.
pests•2h ago
unsnap_biceps•2h ago