Mastering Go’s sync Package: When and How to Use Its Locks
This article explains the different lock types provided by Go's sync package—including Mutex, RWMutex, Cond, WaitGroup, Once, Map, and Pool—detailing their use cases, code examples, and how they help manage concurrency safely and efficiently.
Go's sync package offers a variety of lock primitives for concurrent programming. Locks protect shared resources, preventing data races and ensuring consistency, but choosing the right lock balances safety and performance.
1. File lock
File locks are OS‑level locks, unrelated to sync. The example shows how to open a file, acquire an exclusive lock with syscall.Flock, read the file, and release the lock.
func main() {
var f = "/var/logs/app.log"
file, err := os.OpenFile(f, os.O_RDWR, os.ModeExclusive)
if err != nil {
panic(err)
}
defer file.Close()
// call system lock
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
panic(err)
}
defer syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
// read file content
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
fmt.Printf("%s", all)
time.Sleep(time.Second * 10) // simulate long operation
}Attempting to run the program concurrently results in a panic: panic: resource temporarily unavailable.
2. sync.Mutex
A standard mutual exclusion lock. The example demonstrates a data race when two goroutines increment an int without synchronization.
var i int
func main() {
go add(&i)
time.Sleep(time.Second * 3)
println(i)
}
func add(i *int) {
for j := 0; j < 10000; j++ {
*i = *i + 1
}
}Running the program yields nondeterministic results because of the race. Using the -race flag reveals the conflict.
go run -race main.go
==================
WARNING: DATA RACE
Read at 0x00000056ccb8 by goroutine 7:
main.add()
main.go:23 +0x43
Previous write at 0x00000056ccb8 by goroutine 6:
main.add()
main.go:23 +0x59
Goroutine 7 (running) created at:
main.main()
main.go:14 +0x76
Goroutine 6 (running) created at:
main.main()
main.go:13 +0x52
==================
20000
Found 1 data race(s)
exit status 66Fixing the race by protecting the increment with a mutex:
func add(i *int) {
for j := 0; j < 10000; j++ {
s.Lock()
*i = *i + 1
s.Unlock()
}
}3. sync.RWMutex
An upgraded lock that allows multiple readers but exclusive writers. Its API includes Lock, Unlock, RLock, RUnlock, and RLocker.
func (rw *RWMutex) Lock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RLocker() Locker
func (rw *RWMutex) RUnlock()
func (rw *RWMutex) Unlock()4. sync.Map
A concurrent map that embeds its own lock, avoiding the need for an external mutex.
type User struct {
m map[string]string
l sync.Mutex
}
var m sync.Map
func main() {
m.Store("1", 1)
m.Store("2", 1)
m.Store("3", 1)
m.Store(4, "5") // note type
load, ok := m.Load("1")
if ok {
fmt.Printf("%v
", load)
}
load, ok = m.Load(4)
if ok {
fmt.Printf("%v
", load)
}
}Keys and values are of type interface{}, so type assertions are required when retrieving data.
5. sync.Once
Ensures a function runs only once, useful for lazy initialization or singleton patterns.
package main
import "sync"
var once sync.Once
func main() {
doOnce()
}
func doOnce() {
once.Do(func() {
println("one")
})
}6. sync.Cond
A condition variable that blocks goroutines until a specific condition is signaled.
package main
import (
"sync"
"time"
)
var cond = sync.NewCond(&sync.Mutex{})
func main() {
for i := 0; i < 10; i++ {
go func(i int) {
cond.L.Lock()
cond.Wait() // wait for signal
println(i)
cond.L.Unlock()
}(i)
}
time.Sleep(time.Second * 1) // ensure all goroutines start
cond.Signal()
time.Sleep(time.Second * 1) // give output time
}Replacing Signal with Broadcast wakes all waiting goroutines.
7. sync.WaitGroup
Coordinates the completion of a set of goroutines.
package main
import "sync"
var wg sync.WaitGroup
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // increment counter
go func() {
println("1")
wg.Done() // decrement counter
}()
}
wg.Wait() // block until counter is zero
}8. sync.Pool
A pool for temporary objects to reduce allocations. Objects may be reclaimed by GC at any time.
package main
import (
"fmt"
"sync"
)
type User struct {
name string
}
var pool = sync.Pool{
New: func() interface{} {
return User{name: "default name"}
},
}
func main() {
pool.Put(User{name: "name1"})
pool.Put(User{name: "name2"})
fmt.Printf("%v
", pool.Get()) // {name1}
fmt.Printf("%v
", pool.Get()) // {name2}
fmt.Printf("%v
", pool.Get()) // {default name} – pool empty, New is called
}Because sync.Pool is goroutine‑safe, frameworks like Gin use it to cache per‑request contexts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
