waitgroup on subset of go routines
waitgroup on subset of go routines
I have situation where in, the main go routines will create "x" go routines. but it is interested only in "y" ( y < x ) go routines to finish.
I was hoping to use Waitgroup. But Waitgroup only allows me to wait on all go routines. I cannot, for example do this,
1. wg.Add (y)
2 create "x" go routines. These routines will call wg.Done() when finished.
3. wg. Wait()
This panics when the y+1 go routine calls wg.Done() because the wg counter goes negative.
I sure can use channels to solve this but I am interested if Waitgroup solves this.
y
3 Answers
3
As noted in Adrian's answer, sync.WaitGroup is a simple counter whose Wait method will block until the counter value reaches zero. It is intended to allow you to block (or join) on a number of goroutines before allowing a main flow of execution to proceed.
sync.WaitGroup
Wait
The interface of WaitGroup is not sufficiently expressive for your usecase, nor is it designed to be. In particular, you cannot use it naïvely by simply calling wg.Add(y) (where y < x). The call to wg.Done by the (y+1)th goroutine will cause a panic, as it is an error for a wait group to have a negative internal value. Furthermore, we cannot be "smart" by observing the internal counter value of the WaitGroup; this would break an abstraction and, in any event, its internal state is not exported.
WaitGroup
wg.Add(y)
wg.Done
WaitGroup
Implement your own!
You can implement the relevant logic yourself using some channels per the code below (playground link). Observe from the console that 10 goroutines are started, but after two have completed, we fallthrough to continue execution in the main method.
package main
import (
"fmt"
"time"
)
// Set goroutine counts here
const (
// The number of goroutines to spawn
x = 10
// The number of goroutines to wait for completion
// (y <= x) must hold.
y = 2
)
func doSomeWork()
// do something meaningful
time.Sleep(time.Second)
func main()
// Accumulator channel, used by each goroutine to signal completion.
// It is buffered to ensure the [y+1, ..., x) goroutines do not block
// when sending to the channel, which would cause a leak. It will be
// garbage collected when all goroutines end and the channel falls
// out of scope. We receive y values, so only need capacity to receive
// (x-y) remaining values.
accChan := make(chan struct, x-y)
// Spawn "x" goroutines
for i := 0; i < x; i += 1
// Wrap our work function with the local signalling logic
go func(id int, doneChan chan<- struct)
fmt.Printf("starting goroutine #%dn", id)
doSomeWork()
fmt.Printf("goroutine #%d completedn", id)
// Communicate completion of goroutine
doneChan <- struct
(i, accChan)
for doneCount := 0; doneCount < y; doneCount += 1
<-accChan
// Continue working
fmt.Println("Carrying on without waiting for more goroutines")
As this does not wait for the [y+1, ..., x) goroutines to complete, you should take special care in the doSomeWork function to remove or minimize the risk that the work can block indefinitely, which would also cause a leak. Remove, where possible, the feasibility of indefinite blocking on I/O (including channel operations) or falling into infinite loops.
doSomeWork
You could use a context to signal to the additional goroutines when their results are no longer required to have them break out of execution.
context
WaitGroup doesn't actually wait on goroutines, it waits until its internal counter reaches zero. If you only Add() the number of goroutines you care about, and you only call Done() in those goroutines you care about, then Wait() will only block until those goroutines you care about have finished. You are in complete control of the logic and flow, there are no restrictions on what WaitGroup "allows".
WaitGroup
Add()
Done()
Wait()
WaitGroup
Right. But, I dont know, apriori, which of the "y" go routines I care about. Quite simply, of the "x" go routines, I only want to wait for the first "y" routines that finish, not specific ones. Dont care about the remaining x-y go routines.
– Paras Shah
Sep 7 '18 at 18:44
Are these y specific go-routines that you are trying to track, or any y out of the x? What are the criteria?
Update:
1. If you hve control over any criteria to pick matching y go-routines:
matching y
You can do wp.wg.Add(1) and wp.wg.Done() from inside the goroutine based on your condition by passing it as a pointer argument into the goroutine, if your condition can't be checked outside the goroutine.
wp.wg.Add(1)
wp.wg.Done()
Something like below sample code. Will be able to be more specific if you provide more details of what you are trying to do.
func sampleGoroutine(z int, b string, wg *sync.WaitGroup)
defer func()
if contition1
wg.Done()
if contition1
wg.Add(1)
//do stuff
func main()
wg := sync.WaitGroup
for i := 0; i < x; i++
go sampleGoroutine(1, "one", &wg)
wg.Wait()
2. If you have no control over which ones, and just want the first y:
first y
Based on your comment, that you have no control/desire to pick any specific goroutines, but the ones that finish first. If you would want to do it in a generic way, you can use the below custom waitGroup implementation that fits your use case. (It's not copy-safe, though. Also doesn't have/need wg.Add(int) method)
type CountedWait struct
wait chan struct
limit int
func NewCountedWait(limit int) *CountedWait
return &CountedWait
wait: make(chan struct, limit),
limit: limit,
func (cwg *CountedWait) Done()
cwg.wait <- struct
func (cwg *CountedWait) Wait()
count := 0
for count < cwg.limit
<-cwg.wait
count += 1
Which can be used as follows:
func sampleGoroutine(z int, b string, wg *CountedWait)
success := false
defer func()
if success == true
fmt.Printf("goroutine %d finished successfullyn", z)
wg.Done()
()
fmt.Printf("goroutine %d startedn", z)
time.Sleep(time.Second)
if rand.Intn(10)%2 == 0
success = true
func main()
x := 10
y := 3
wg := NewCountedWait(y)
for i := 0; i < x; i += 1
// Wrap our work function with the local signalling logic
go sampleGoroutine(i, "something", wg)
wg.Wait()
fmt.Printf("%d out of %d goroutines finished successfully.n", y, x)
3. You can also club in context with 2 to ensure that the remaining goroutines don't leak
You may not be able to run this on play.golang, as it has some long sleeps.
context
Below is a sample output:
(note that, there may be more than y=3 goroutines marking Done, but you are only waiting till 3 finish)
goroutine 9 started
goroutine 0 started
goroutine 1 started
goroutine 2 started
goroutine 3 started
goroutine 4 started
goroutine 5 started
goroutine 5 marking done
goroutine 6 started
goroutine 7 started
goroutine 7 marking done
goroutine 8 started
goroutine 3 marking done
continuing after 3 out of 10 goroutines finished successfully.
goroutine 9 will be killed, bcz cancel
goroutine 8 will be killed, bcz cancel
goroutine 6 will be killed, bcz cancel
goroutine 1 will be killed, bcz cancel
goroutine 0 will be killed, bcz cancel
goroutine 4 will be killed, bcz cancel
goroutine 2 will be killed, bcz cancel
goroutine 9 started
goroutine 0 started
goroutine 1 started
goroutine 2 started
goroutine 3 started
goroutine 4 started
goroutine 5 started
goroutine 5 marking done
goroutine 6 started
goroutine 7 started
goroutine 7 marking done
goroutine 8 started
goroutine 3 marking done
continuing after 3 out of 10 goroutines finished successfully.
goroutine 9 will be killed, bcz cancel
goroutine 8 will be killed, bcz cancel
goroutine 6 will be killed, bcz cancel
goroutine 1 will be killed, bcz cancel
goroutine 0 will be killed, bcz cancel
goroutine 4 will be killed, bcz cancel
goroutine 2 will be killed, bcz cancel
Play links
Any "y" out of "x". Think of this as a race of "x" go routines. I am only interested in the top "y" finishers.
– Paras Shah
Sep 7 '18 at 18:45
Updated the answer. Does it help now?
– Tushar
Sep 12 '18 at 21:11
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Is the number of goroutines,
y, which the main flow of execution should wait on, fixed, or is it dynamically determined by a computed result?– Cosmic Ossifrage
Sep 7 '18 at 18:48