The sync Package

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
    }
}