A sync.Cond
is a synchronization primitive that allows goroutines to wait for or signal events. It provides a way to block a goroutine until some condition is met, and then notify other goroutines when the condition has changed.
This is especialcly useful in scenarios where goroutines need to synchronize and wait for certain conditions, such as when one goroutine is producing data and another is consuming it, or when you want to control access to a shared resource.
- Waiting: A goroutine can wait on a
sync.Cond
object until it is notified that the condition it’s waiting for is satisfied. - Signaling: A goroutine can signal others waiting on a condition to proceed when a certain condition is met.
- Broadcasting: A goroutine can notify all waiting goroutines, allowing them to wake up and re-check the condition.
NewCond(l sync.Locker)
: This function creates a new condition variable that uses the provided lock (sync.Locker
), such assync.Mutex
orsync.RWMutex
, to synchronize access.Wait()
: The goroutine that calls this method will block until it is notified by another goroutine (viaSignal
orBroadcast
).Signal()
: Notifies one goroutine that is waiting on the condition to wake up and re-check the condition.Broadcast()
: Notifies all goroutines that are waiting on the condition to wake up and re-check the condition.
package main
import (
"fmt"
"sync"
)
var (
cond = sync.NewCond(&sync.Mutex{})
count = 0
)
// Consumer goroutine
func consumer() {
cond.L.Lock()
for count <= 0 {
cond.Wait() // Wait until count > 0
}
count--
fmt.Println("Consumed 1 item, remaining:", count)
cond.L.Unlock()
}
// Producer goroutine
func producer() {
cond.L.Lock()
count++
fmt.Println("Produced 1 item, total:", count)
cond.Signal() // Signal a waiting consumer
cond.L.Unlock()
}
func main() {
var wg sync.WaitGroup
// Start 3 consumers
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
consumer()
}()
}
// Start 2 producers
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
producer()
}()
}
wg.Wait()
}
cond.Wait()
: Used when a goroutine wants to wait until some condition becomes true. It automatically releases the lock and blocks until it’s notified.cond.Signal()
: Used to notify one goroutine that’s waiting on the condition variable. It’s typically used when some state change has occurred and other goroutines may proceed.cond.Broadcast()
: Used to notify all waiting goroutines, allowing them to wake up and check their conditions.
-
Producer-Consumer Problem: A common scenario where
sync.Cond
is useful is when one or more goroutines are producing data (e.g., items, messages) and other goroutines are consuming it. Producers need to signal consumers when new data is available, and consumers need to wait when the data is not yet available. -
Worker Pool: A worker pool where multiple workers wait for tasks and work when the tasks are available.
While tools like sync.Mutex
, sync.RWMutex
, and sync.WaitGroup
are useful for synchronizing goroutines, they cannot handle waiting for a specific condition or signaling other goroutines when a condition is met. Here's where sync.Cond
shines:
-
For synchronizing goroutines based on conditions:
sync.Mutex
andsync.RWMutex
are good for mutual exclusion (ensuring only one goroutine accesses a resource at a time), but they cannot directly handle scenarios where a goroutine needs to wait for a condition to change.sync.WaitGroup
helps wait for goroutines to complete, but it doesn't support waiting for specific conditions or notifying others to continue based on dynamic states.
-
Handling producer-consumer scenarios:
- If you are implementing producer-consumer patterns,
sync.Cond
allows consumers to wait for producers to add data to the buffer and ensures that consumers can act when data is available, without constantly polling the buffer. Withoutsync.Cond
, you would need additional mechanisms to handle these waiting/notification states, which could lead to inefficient or error-prone code.
- If you are implementing producer-consumer patterns,
-
Efficient use of resources:
- With
sync.Cond
, a goroutine doesn't constantly check a condition in a tight loop (which could waste CPU resources). It simply waits for a signal and wakes up only when necessary.
- With
-
Always use a lock with
sync.Cond
: Async.Cond
must be used with async.Locker
, such assync.Mutex
orsync.RWMutex
, to synchronize access to shared resources. -
Avoid deadlocks: Ensure that goroutines calling
Wait
will eventually be notified viaSignal
orBroadcast
. Otherwise, they will remain blocked forever, causing a deadlock. -
Minimize contention: Only use
Signal
orBroadcast
when necessary, as notifying many goroutines at once can cause high contention and reduce performance.