-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: sync: add Go method to WaitGroup to launch a tracked goroutine #63796
Comments
This is a repeat of #18022, which was re-oriented to become a vet check, but I'm still highly in support of a The tailscale codebase has an extension to As an additional data point, the |
There's also #57534 to just add errgroup in std |
@dsnet Thanks for the link. GitHub search just doesn't work and didn't help me to (re)discover prior proposals. |
ISTM that sync might get a v2 to add typed sync.Map. It could be worth waiting for that to then adopt the errgroup methods on a new type. |
Maybe |
A WaitGroup with a Go method is not very different from https://pkg.go.dev/cmd/go/internal/par#Queue. I would kind of rather see that moved out of internal and into sync/par. It's a much higher level library than the sync primitives. |
I do think this would be nice to have on wait group. A very relevant piece of writing is this: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/. He argues that At a previous employer we used https://github.com/ConradIrwin/parallel instead of either waitgroup or I'm not sure that |
I was also inspired by the structured concurrency post when I wrote my helper library https://github.com/carlmjohnson/flowmatic. It's a little tricky because the standard library needs to have low level primitives so that people can build higher level abstractions like Map and whatnot, but it would be good to have a standard set of high level abstractions too. |
I've also wanted |
One possible argument against it is that use of it requires the function to close over variable that may be mutating over the lifetime of the asynchronously running goroutine. As it stands today, people can write the more safe: wg.Add(1)
go func(a int, b string) {
defer wg.Done()
...
}(a, b) where However, my personal experience is that I've encountered more bugs due to calling go func(a int, b string) {
wg.Add(1)
defer wg.Done()
...
}() |
Similar and related: #63941 |
Very useful feature, #63941 is the same, to provide developers with less error-prone api, community third-party libraries have also been implemented |
Very useful and straightforward, wondering if Add()/Done() is really needed when this Go() available. |
I'd like to get this back on the active proposal track as I believe the main concern with At Tailscale we have the I have discovered the vast majority of existing var wg sync.WaitGroup
defer wg.Wait()
for i, v := range ... {
wg.Add(1)
go func(i int, v T) {
defer wg.Done()
... // use i and v
}(i, v)
} However, this extra level of parameter passing is no longer necessary in modern Go. var wg syncs.WaitGroup
defer wg.Wait()
for i, v := range ... {
wg.Go(func() {
... // use i and v
})
} because |
Also, I should note that the bug that |
Just be careful when the passed loop-var parameters are modified in the Go method function body. [edit]: this note is actually only valid for traditional 3-clause for-loops. for i := 0, condition, postStatement {
wg.Go(func() {
... // Since Go 1.22, DON'T modify i here!!! No matter how the modification is synchronized.
// Before Go 1.22, the modification might be okay if it is synchronized well.
})
} |
TL;DR
Add a
func (*WaitGroup) Go(task func())
method to launch a task in a goroutine tracked with async.WaitGroup
.Combined with the loopvar change (#60078), writing parallel code would be much less error prone.
Rationale
A very common use case for
sync.WaitGroup
is to track the termination of tasks launched in goroutines.Here is the classic example:
I propose to add a
func (*WaitGroup) Go(func())
method that would wrap:wg.Add(1)
: the1
looks like a magic valuego
keyword and the()
after the func body are magic for Go beginnersdefer wg.Done()
: thedefer
keyword and the appearance ofDone
before the call to the worker are magicA simple implementation:
The example above would be much reduced and many footguns avoided (the last remaining footgun is being addressed by #60078):
The full modified example, including an extended implementation of
sync.WaitGroup
, is available on the Go playground.(to handle the case of a task with arguments, I would recommend rewriting the
work
to use the builder pattern: https://go.dev/play/p/g1Um_GhQOyc)The text was updated successfully, but these errors were encountered: