In concurrent programming within Go, the sync package does a lot of heavy lifting. Among its offerings, the sync.Cond type provides the building blocks necessary for crafting custom synchronization primitives safely. Let’s delve into using sync.Cond for achieving concurrent execution with signal-based synchronization.
Understanding sync.Cond
sync.Cond is a struct designed to manage goroutines with a condition. It ties together a mutex and a waiting condition, allowing goroutines to signal each other when state changes occur.
The basic operations for a sync.Cond are:
Wait(): A call to this method suspends execution of the calling goroutine’s case, releasing the associated lock.Signal(): This method wakes up one of the goroutines waiting on theCond(if there’s any).Broadcast(): Invoked to wake all waiting goroutines.
Basic Example of sync.Cond
Below is a simple example illustrating how sync.Cond can be set up and used:
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready = false
func waitGoroutine() {
cond.L.Lock()
for !ready {
cond.Wait()
}
fmt.Println("Goroutine: Received signal")
cond.L.Unlock()
}
func signalFunction() {
time.Sleep(2 * time.Second) // Simulate work
cond.L.Lock()
ready = true
fmt.Println("Signaler: Sending signal")
cond.Signal()
cond.L.Unlock()
}
func main() {
go waitGoroutine()
signalFunction()
}
In this example, the waitGoroutine waits until the ready boolean becomes true. On reaching readiness, the signalFunction signals the change, allowing the waiting goroutine to resume.
Crafting Custom Synchronization
Beyond simple signaling, sync.Cond can help in creating advanced primitives. Consider a simple semaphore pattern:
package main
import (
"fmt"
"sync"
)
type Semaphore struct {
v int
mtx sync.Mutex
cond *sync.Cond
}
func (s *Semaphore) Acquire() {
s.mtx.Lock()
for s.v <= 0 {
s.cond.Wait()
}
s.v--
s.mtx.Unlock()
}
func (s *Semaphore) Release() {
s.mtx.Lock()
s.v++
s.cond.Signal()
s.mtx.Unlock()
}
func NewSemaphore(initial int) *Semaphore {
sem := Semaphore{v: initial}
sem.cond = sync.NewCond(&sem.mtx)
return &sem
}
func main() {
sem := NewSemaphore(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
sem.Acquire()
fmt.Println("Goroutine 1: acquired the semaphore")
sem.Release()
}()
go func() {
defer wg.Done()
sem.Acquire()
fmt.Println("Goroutine 2: acquired the semaphore")
sem.Release()
}()
wg.Wait()
}
Here, a semaphore is implemented with an adjustable initial value, allowing controlled access to a limited resource, demonstrating a real-world application of sync.Cond.
Conclusion
The sync.Cond type offers powerful capabilities for signaling and coordinating state among Go’s goroutines. By harnessing these features, you can create efficient, custom synchronization methods tailored for specific application needs.