I have an application where I am creating multiple goroutines to perform a certain task concurrently. All worker goroutines wait for a condition/event to occur and once the event is triggered, they begin their execution. After creating all goroutines, the main thread should know that all goroutines are indeed in waiting state before sending a broadcast signal.
I know this can be done using channels (which is something that is recommended) but I also found go's sync package interesting. Just trying to figure out how to achieve the same functionality using the sync package instead of channels
package main
import (
"fmt"
"sync"
"time"
)
var counter int
func worker(wg *sync.WaitGroup, cond *sync.Cond, id int) {
fmt.Println("Starting Goroutine ID:", id)
// Get a lock and wait
cond.L.Lock()
defer cond.L.Unlock()
fmt.Println("Goroutine with ID: ", id, "obtained a lock")
// Do some processing with the shared resource and wait
counter++
wg.Done()
cond.Wait()
fmt.Println("Goroutine ID:", id, "signalled. Continuing...")
}
func main() {
var wg sync.WaitGroup
cond := sync.NewCond(&sync.Mutex{})
counter = 0
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg, cond, i)
}
wg.Wait() // Wait()'ing only until the counter is incremented
// How to make sure that all goroutines you created are indeed wait()'ing ?????
cond.Broadcast()
time.Sleep(2 * time.Second)
cond.Broadcast()
fmt.Println("Final value of the counter is", counter)
}
If I don't use the below statements in the last 3 lines (except the fmt.Println)
time.Sleep(2 * time.Second)
cond.Broadcast()
I get the below output..
Starting Goroutine ID: 4
Goroutine with ID: 4 obtained a lock
Starting Goroutine ID: 3
Goroutine with ID: 3 obtained a lock
Starting Goroutine ID: 1
Goroutine with ID: 1 obtained a lock
Starting Goroutine ID: 0
Goroutine with ID: 0 obtained a lock
Starting Goroutine ID: 2
Goroutine with ID: 2 obtained a lock
Final value of the counter is 5
Goroutine ID: 3 signalled. Continuing...
Ideally every goroutine should be able to print
Goroutine ID: 3 signalled. Continuing...
with the corresponding goroutine id. We are not able to print that because not all goroutines are signalled as some of them are not even in the wait state. That is the reason I added time.Sleep which is not a practical solution.
My question is..How can I know that all goroutines are actually waiting on the condition cond.Wait()..Channels is a solution but I want to know how I could do this using go's sync package?
You have a problem in that the Broadcast
is not guaranteed to wake up all the goroutines since it only wakes up goroutines that are already waiting, and there's a small window between wg.Done()
and cond.Wait()
. Normally, a condition variable would be used with something that represents the "condition" that you're using for synchronization. In this case, you could have a bool
package variable that indicates whether it's OK for the goroutines to continue. main
would set that and then do a broadcast to tell the goroutines to continue. For example:
package main
import (
"fmt"
"sync"
)
var counter int
var start bool
func worker(wg *sync.WaitGroup, cond *sync.Cond, id int) {
fmt.Println("Starting Goroutine ID:", id)
// Get a lock and wait
cond.L.Lock()
fmt.Println("Goroutine with ID: ", id, "obtained a lock")
// Do some processing with the shared resource and wait
counter++
if !start {
cond.Wait()
}
cond.L.Unlock()
fmt.Println("Goroutine ID:", id, "signalled. Continuing...")
wg.Done() // Worker is completely done
}
func main() {
var wg sync.WaitGroup
cond := sync.NewCond(&sync.Mutex{})
counter = 0
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg, cond, i)
}
cond.L.Lock()
start = true
cond.Broadcast()
cond.L.Unlock()
wg.Wait() // Wait until all workers are done
fmt.Println("Final value of the counter is", counter)
}
The addition of the start
variable makes it impossible for the goroutines to not continue when main
tells them to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With