I'm writing a program where I start N (N is a command-line argument) worker threads, and at any time 0 to N-1 of them can be waiting on another to update a variable. What's the best way for the threads to wait for this event, and the best way for one of the threads to notify all the others at once of the event occurring?
sync.Cond
isn't appropriate because the threads don't need to lock a resource upon waking from sleep. sync.WaitGroup
won't work because I don't know how many times to call wg.Done()
.
Solution #1: I could use a sync.Mutex
and have the thread that will eventually notify the others acquire the lock and then unlock it to notify the others, but it seems really inefficient for the others to all fight over a lock when they all just need to pop out of sleep, read a variable to see if that particular worker is now the master, and then either go back to sleep or start working.
Solution #2: Create a wrapper for sync.WaitGroup
that allows keeping track of the number of waiting threads so that I can call wg.Add(-numWaitingThreads)
to wake them. This sounds like a headache to figure out how to code it without all sorts of race conditions.
Solution #3: Until someone comes up with a better idea, I'll be using a list of N channels and have the notifier non-blocking-send to all of the channels except its own. Is this really the best way?
CodePudding user response:
sync.WaitGroup won't work because I don't know how many times to call wg.Done().
wg can be used in this case. Make a wg
with count 1 and pass this to the N goroutines. Make them wg.Wait()
, except the one that updates the variable.
The goroutine updating the variable calls wg.Done()
after successful update thus resulting in N goroutines to come out of wait and start executing further.
CodePudding user response:
To me it does seem like this is an appropriate use case for sync.Cond
. You can use a *RWMutex.RLocker()
for Cond.L
so all goroutines can acquire the read lock simultaneously once the Cond.Broadcast()
is sent.
Additionally, it may be worth making sure you hold a write lock when changing this "who's master" variable to avoid race conditions, which would make sync.Cond
an even better fit.
CodePudding user response:
WaitGroup
is the best option. The reason is that is keeps its signalled state and you are safe from deadlock if the main thread signals too early.
If you use Cond
there is a risk that the main thread calls cond.Broadcast
BEFORE the worker thread calls cond.Wait()
. Since Cond
doesn't remember that it was signalled, the worker thread will wait for the event to happen.
Here is an example: https://go.dev/play/p/YLfvEGO2A18
The main thread broadcasts too early, the worker threads run into a deadlock.
Same case with con.WaitGroup
: https://go.dev/play/p/R6_-ULo2eJ2
The main thread releases the wait group too early, but there is no deadlock.