sync.Mutex 适合保护共享变量,需在访问前 Lock、结束后 Unlock;避免返回带锁字段指针;sync.Once 保证初始化仅一次且不可重置;sync.WaitGroup 要先 Add 再 go,用 defer Done;sync.RWMutex 适用于读多写少场景。
sync.Mutex 适合保护共享变量,但别在返回值里传指针
多个 goroutine 同时读写一个
、
或结构体字段时,不加锁大概率触发
或读到脏数据。用
最直接:在访问前调用
,结束后立刻
。
常见错误是把带锁的结构体字段以指针形式返回,比如:
应该改为只返回值,或提供受控的修改方法:
返回副本:
封装操作:
避免暴露内部状态,尤其别让调用方决定何时解锁
sync.Once 保证初始化只执行一次,但不能重置
的
方法适合做单例初始化、配置加载、资源首次创建等场景。它内部用原子操作 + 互斥锁实现,线程安全且开销极小。
立即学习
“
go语言免费学习笔记(深入)
”;
注意它不可重置、不可重复使用。一旦
执行过,再调用同个
实例的
将直接返回,不管
是否 panic(panic 会导致该次调用失败,但标记仍设为已执行)。
灵活路由的PHP库
灵活路由的PHP库
下载
不要试图通过新建
变量来“重试”初始化
如果初始化逻辑可能失败且需重试,应自己封装重试逻辑,而不是依赖
典型误用:
—— 这不会重试,因为
已标记完成
sync.WaitGroup 用于等待 goroutine 结束,别漏掉 Add 或 Done
是等待一组 goroutine 完成的最常用方式。核心是三步:
、启动 goroutine、每个 goroutine 结尾调用
,主 goroutine 调用
阻塞等待。
最容易出错的是调用时机:
必须在
语句之前,否则可能
已返回而 goroutine 还没被计数;
必须确保执行,哪怕函数 panic,建议用
。
错误写法:
——
在 goroutine 内,竞态风险高
正确写法:
如果 goroutine 数量动态,先算好数量再
,别靠
之类估算
sync.RWMutex 读多写少时提升性能,但写锁会阻塞所有读
当共享数据读操作远多于写操作(比如配置缓存、
路由
表),
比普通
更高效:多个 goroutine 可同时持有读锁,但写锁会独占,且会阻塞新读锁请求(已有读锁不强制释放)。
关键点在于锁粒度和使用习惯:读锁用
,写锁仍用
。混用时注意——写锁必须等所有当前读锁释放后才能获取。
别在持有读锁时调用可能升级为写锁的函数(Go 不支持锁升级)
写操作频繁时,
可能比
更慢,因读锁维护有额外开销
用
观察
相关阻塞事件,确认是否真因读锁堆积导致延迟
实际并发控制中,最难的不是选哪个类型,而是判断“哪里需要同步”和“锁的边界是否覆盖全部临界区”。很多 bug 出现在看似无害的辅助函数里——比如一个日志函数悄悄修改了全局计数器,却没加锁。
intmapfatal error: concurrent map writessync.Mutexmu.Lock()mu.Unlock()func (c *Counter) GetPtr() *int {
c.mu.Lock()
return &c.val // 危险!外部可绕过锁直接改
}return c.valfunc (c *Counter) Inc() { c.mu.Lock(); defer c.mu.Unlock(); c.val++ }sync.OnceDo()once.Do(f)onceDo()fsync.Oncesync.Onceif err := initDB(); err != nil { once.Do(initDB) }oncesync.WaitGroupwg.Add(n)wg.Done()wg.Wait()Add()goWait()Done()defer wg.Done()go func() { wg.Add(1); ...; wg.Done() }()Addwg.Add(1); go func() { defer wg.Done(); ... }()Addlen(ch)sync.RWMutexMutexRLock()/RUnlock()Lock()/Unlock()RWMutexMutexgo tool tracesync