The sync Package
Mutex
type Mutex struct {
state int32 // lock state values defined below
sema uint32 // semaphore for signaling blocked goroutines
}
const (
mutexLocked = 1 << iota // 1 means locked
mutexWoken // 2 means awakened
mutexWaiterShift = iota // 2 shift value for counting waiting goroutines
)When there’s no contention, mutexes work simply. When there is contention, they first spin because most code protected by mutexes runs quickly enough that spinning briefly works well. If spinning fails, the current goroutine enters Gwaiting state via the semaphore.
Once
type Once struct {
m Mutex
done uint32
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}This implements double-checked locking. It returns quickly when already done and prevents f from running twice while it’s still executing.
Condition Variables
Use sync.NewCond(l locker) to create a condition variable. The parameter can be a mutex or read-write lock. It returns a *sync.Cond.
Three methods exist: Wait, Signal, and Broadcast for waiting for notification, signaling one waiter, and signaling all waiters respectively. Use condition variables when you need the thundering herd effect (waking everyone).
Wait lets the current goroutine temporarily give up a lock and block waiting for notification. This allows other goroutines to write, then call signal to wake the waiting goroutine to continue reading.
When pairing with read-write locks:
- Always lock the associated read lock before calling Wait
- Always unlock the associated lock after reading
Unlike Wait, Signal and Broadcast don’t require holding the associated lock.
Atomic Operations
These help in lock-free programming. Copy-on-write during modification is another lock-free approach.
To decrease a uint by N:
// Exploit two's complement to bypass compiler checks
atomic.AddUint32(&ui32, ^uint32(-N-1))CAS Operations
Sometimes we need to keep trying CAS operations in a loop until they return true:
for {
old := atomic.LoadInt32(&value)
new := someOperation(old)
if atomic.CompareAndSwapInt32(&value, old, new) {
break
}
}