Skip to main content
Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.
Values containing the types defined in this package should not be copied after first use.

Core Types

Mutex

A Mutex is a mutual exclusion lock. The zero value for a Mutex is an unlocked mutex.
var mu sync.Mutex

mu.Lock()
defer mu.Unlock()
// critical section
Lock
func()
Locks the mutex. If the lock is already in use, the calling goroutine blocks until the mutex is available.
Unlock
func()
Unlocks the mutex. It is a run-time error if the mutex is not locked on entry to Unlock.
TryLock
func() bool
Tries to lock the mutex and reports whether it succeeded. Note that while correct uses of TryLock exist, they are rare.
A locked Mutex is not associated with a particular goroutine. It is allowed for one goroutine to lock a Mutex and then arrange for another goroutine to unlock it.

RWMutex

A RWMutex is a reader/writer mutual exclusion lock. The lock can be held by an arbitrary number of readers or a single writer.
var rw sync.RWMutex

// Multiple readers can hold the lock simultaneously
rw.RLock()
defer rw.RUnlock()
// read operations

// Only one writer can hold the lock
rw.Lock()
defer rw.Unlock()
// write operations
RLock
func()
Locks rw for reading. Should not be used for recursive read locking.
RUnlock
func()
Undoes a single RLock call.
Lock
func()
Locks rw for writing. If the lock is already locked for reading or writing, Lock blocks until the lock is available.
Unlock
func()
Unlocks rw for writing.
TryRLock
func() bool
Tries to lock rw for reading and reports whether it succeeded.
TryLock
func() bool
Tries to lock rw for writing and reports whether it succeeded.
RLocker
func() Locker
Returns a Locker interface that implements Lock and Unlock by calling rw.RLock and rw.RUnlock.

WaitGroup

A WaitGroup is a counting semaphore typically used to wait for a group of goroutines or tasks to finish.
var wg sync.WaitGroup

// Using the Go method (recommended)
wg.Go(task1)
wg.Go(task2)
wg.Wait()

// Using Add/Done (traditional pattern)
wg.Add(1)
go func() {
    defer wg.Done()
    // task work
}()
wg.Wait()
Add
func(delta int)
Adds delta to the WaitGroup counter. If the counter becomes zero, all goroutines blocked on Wait are released. Callers should prefer WaitGroup.Go.
Done
func()
Decrements the WaitGroup counter by one. Equivalent to Add(-1). Callers should prefer WaitGroup.Go.
Wait
func()
Blocks until the WaitGroup counter is zero.
Go
func(f func())
Calls f in a new goroutine and adds that task to the WaitGroup. When f returns, the task is removed from the WaitGroup. The function f must not panic.

Once

Once is an object that will perform exactly one action.
var once sync.Once

once.Do(func() {
    // initialization code
    // will only run once
})
Do
func(f func())
Calls the function f if and only if Do is being called for the first time for this instance of Once. Multiple calls will only invoke f once.

Cond

Cond implements a condition variable, a rendezvous point for goroutines waiting for or announcing the occurrence of an event.
var mu sync.Mutex
c := sync.NewCond(&mu)

// Waiter goroutine
c.L.Lock()
for !condition() {
    c.Wait()
}
// make use of condition
c.L.Unlock()

// Signaler goroutine
c.L.Lock()
// change condition
c.Signal() // or c.Broadcast()
c.L.Unlock()
NewCond
func(l Locker) *Cond
Returns a new Cond with Locker l.
Wait
func()
Atomically unlocks c.L and suspends execution of the calling goroutine. After later resuming execution, Wait locks c.L before returning.
Signal
func()
Wakes one goroutine waiting on c, if there is any.
Broadcast
func()
Wakes all goroutines waiting on c.
For many simple use cases, users will be better off using channels than a Cond (Broadcast corresponds to closing a channel, and Signal corresponds to sending on a channel).

Pool

A Pool is a set of temporary objects that may be individually saved and retrieved. A Pool is safe for use by multiple goroutines simultaneously.
var bufferPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer)
    },
}

// Get a buffer from the pool
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)

// use buffer
Get
func() any
Selects an arbitrary item from the Pool, removes it from the Pool, and returns it to the caller. If Get would otherwise return nil and p.New is non-nil, Get returns the result of calling p.New.
Put
func(x any)
Adds x to the pool.
New
func() any
Optionally specifies a function to generate a value when Get would otherwise return nil.

Map

Map is like a Go map[any]any but is safe for concurrent use by multiple goroutines without additional locking or coordination.
var m sync.Map

// Store a value
m.Store("key", "value")

// Load a value
if value, ok := m.Load("key"); ok {
    fmt.Println(value)
}

// Delete a value
m.Delete("key")

// Iterate over all key-value pairs
m.Range(func(key, value any) bool {
    fmt.Printf("%v: %v\n", key, value)
    return true // continue iteration
})
Load
func(key any) (value any, ok bool)
Returns the value stored in the map for a key, or nil if no value is present.
Store
func(key, value any)
Sets the value for a key.
LoadOrStore
func(key, value any) (actual any, loaded bool)
Returns the existing value for the key if present. Otherwise, it stores and returns the given value.
LoadAndDelete
func(key any) (value any, loaded bool)
Deletes the value for a key, returning the previous value if any.
Delete
func(key any)
Deletes the value for a key.
Swap
func(key, value any) (previous any, loaded bool)
Swaps the value for a key and returns the previous value if any.
CompareAndSwap
func(key, old, new any) bool
Swaps the old and new values for key if the value stored in the map is equal to old.
CompareAndDelete
func(key, old any) bool
Deletes the entry for key if its value is equal to old.
Range
func(f func(key, value any) bool)
Calls f sequentially for each key and value present in the map. If f returns false, Range stops the iteration.
Clear
func()
Deletes all the entries, resulting in an empty Map.

Interfaces

Locker

type Locker interface {
    Lock()
    Unlock()
}
A Locker represents an object that can be locked and unlocked.

Best Practices

For most synchronization needs, channels provide better composability and are easier to reason about than low-level primitives like Mutex and Cond.
The Go method is simpler and less error-prone than manually managing Add/Done calls. It automatically handles the counter and ensures Done is called even if the function panics.
All sync types contain internal state that must not be copied. Always pass pointers to these types.
Always defer Unlock calls immediately after Lock to ensure the lock is released even if the function panics.
mu.Lock()
defer mu.Unlock()
// critical section
Use Pool to cache allocated but unused items for later reuse, reducing GC pressure. Don’t use Pool for managing shared state or as a general cache.

Common Patterns

Protecting shared state with Mutex

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

Concurrent workers with WaitGroup

var wg sync.WaitGroup
tasks := []func(){task1, task2, task3}

for _, task := range tasks {
    wg.Go(task)
}

wg.Wait() // Wait for all tasks to complete

Lazy initialization with Once

var (
    config *Config
    once   sync.Once
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

Object pooling for performance

var jsonEncoderPool = sync.Pool{
    New: func() any {
        return json.NewEncoder(nil)
    },
}

func EncodeJSON(w io.Writer, v any) error {
    enc := jsonEncoderPool.Get().(*json.Encoder)
    defer jsonEncoderPool.Put(enc)
    
    enc.Reset(w)
    return enc.Encode(v)
}

Build docs developers (and LLMs) love