Go 入门(23):sync 包——Mutex、Once、Pool
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 23 篇,接 select 多路复用。sync 包提供基础同步原语,Mutex 保护共享数据,Once 确保只执行一次,Pool 减少对象分配。
本文要回答的问题
- Mutex 怎么用?Lock 和 Unlock 要配对,不配对会怎样?
- RWMutex 是什么?什么时候用读锁什么时候用写锁?
- sync.Once 做什么?还有什么"只执行一次"的方式?
- sync.Pool 是什么?什么时候用?
一、sync.Mutex:互斥锁
保护共享数据不被多个 goroutine 同时修改:
go
var counter int
var mu sync.Mutex
func increment() {
mu.Lock() // 加锁
counter++ // 安全操作共享数据
mu.Unlock() // 解锁
}
// 使用
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println(counter) // 1000规则:
Lock()和Unlock()必须配对使用- 忘记 Unlock 会导致死锁——其他 goroutine 永远等不到锁
- 用
defer mu.Unlock()确保解锁
二、sync.RWMutex:读写锁
读多写少的场景,用 RWMutex 比 Mutex 更高效:
go
type Cache struct {
data map[string]string
mu sync.RWMutex
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // 读锁,多个 goroutine 可以同时读
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // 写锁,排他,阻塞所有读和写
defer c.mu.Unlock()
c.data[key] = value
}| 场景 | 用什么锁 |
|---|---|
| 只有一个 goroutine 写,其他读 | RWMutex(读不阻塞读) |
| 写频繁 | Mutex 就够了 |
| 读多写少 | RWMutex |
三、sync.Once:只执行一次
go
var once sync.Once
var config *Config
func loadConfig() {
once.Do(func() {
config = loadConfigFromFile() // 只执行一次
})
}多个 goroutine 同时调用 loadConfig(),once.Do 保证传入的函数只执行一次,其他 goroutine 会阻塞等待完成后返回。
用途:延迟初始化、单例模式。
四、sync.Pool:对象池
Pool 用来缓存临时对象,减少分配和 GC 压力:
go
var bufPool = sync.Pool{
New: func() any {
return bytes.NewBuffer(make([]byte, 0, 1024))
},
}
func handleRequest() {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
// 使用 buf 处理请求
json.NewEncoder(buf).Encode(data)
// 用完放回池子
}注意:
- Pool 里的对象可能被 GC 回收,不能保证 Get 一定拿到之前放回的对象
- 适合"每次用都差不多,创建成本高"的对象
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘记 Unlock | 死锁,其他 goroutine 永远等 | defer mu.Unlock() |
| 锁了拷贝 | 拷贝的 Mutex 是新的,没锁 | 结构体里放 Mutex 传指针 |
| RWMutex 写锁时读锁返回 | 脏读 | 写锁时 RLock 会等 |
| Pool 对象被回收 | Get 返回 nil | 必须设置 New 函数 |
相关与延伸
下一篇:练习:猜数字游戏;Mutex 底层是 futex,进阶层讲;Go 的 sync 包和 C++ 的 std::mutex 对比,见 C 锁与并发。
一句话总结
Go sync 包:Mutex 保护共享数据,Lock/Unlock 配对 defer 保证解锁;RWMutex 读多写少时用,读锁不阻塞读,写锁排他;Once.Do 确保只执行一次,适合延迟初始化和单例;Pool 缓存临时对象,减少 GC 压力,必须设置 New 函数应对回收。