目录
[一、sync 库核心定位](#一、sync 库核心定位)
[1. sync.Mutex:基础互斥锁](#1. sync.Mutex:基础互斥锁)
[2. sync.RWMutex:读写分离锁](#2. sync.RWMutex:读写分离锁)
[3. sync.WaitGroup:协程等待组](#3. sync.WaitGroup:协程等待组)
[4. sync.Once:一次性执行](#4. sync.Once:一次性执行)
[5. sync.Map:并发安全的 Map](#5. sync.Map:并发安全的 Map)
[6. sync.Pool:临时对象池](#6. sync.Pool:临时对象池)
[7. sync.Cond:条件变量](#7. sync.Cond:条件变量)
[实战示例:生产者 - 消费者模型](#实战示例:生产者 - 消费者模型)
[8. sync/atomic:原子操作(附属子包)](#8. sync/atomic:原子操作(附属子包))
[三、sync 库使用原则](#三、sync 库使用原则)
一、sync 库核心定位
sync库解决的是单个 Go 进程内的协程同步问题,所有组件均基于进程内内存实现,无法跨进程 / 跨机器生效。其核心价值是:以极简的 API 实现并发安全,避免开发者重复造轮子。
二、核心组件全解析
1. sync.Mutex:基础互斥锁
核心作用
保证同一时间只有一个协程进入临界区,是解决共享资源竞争的基础工具。
关键特性
- 排他锁:加锁后其他协程阻塞,直到解锁;
- 非可重入:同一协程重复加锁会导致死锁(区别于 Java 的 ReentrantLock);
- 无超时机制:阻塞时只能等解锁,无法主动退出。
实战示例:保护共享计数器
package main
import (
"fmt"
"sync"
)
var (
count int
lock sync.Mutex
)
// 累加函数(并发安全)
func add(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
lock.Lock() // 加锁,进入临界区
count++ // 共享资源操作(非原子性)
lock.Unlock() // 解锁,释放临界区
}
}
func main() {
var wg sync.WaitGroup
wg.Add(10) // 等待10个协程
// 启动10个协程并发累加
for i := 0; i < 10; i++ {
go add(&wg)
}
wg.Wait()
fmt.Printf("最终计数:%d(预期10000)\n", count) // 精准输出10000
}
避坑要点
- 解锁必须在
defer中:防止临界区 panic 导致锁泄漏; - 最小锁粒度:仅对 "共享资源操作" 加锁,避免锁包裹整个函数。
2. sync.RWMutex:读写分离锁
核心作用
针对 "多读少写" 场景优化,读操作可并发,写操作独占,性能优于 Mutex。
关键特性
- 读锁(RLock/RUnlock):多个协程可同时获取,不阻塞其他读锁;
- 写锁(Lock/Unlock):独占锁,阻塞所有读 / 写锁;
- 读写互斥:写锁未释放时,读锁无法获取;读锁未释放时,写锁阻塞。
实战示例:高并发缓存读写
package main
import (
"fmt"
"sync"
"time"
)
var (
// 全局缓存(key=用户ID,value=用户名)
userCache = make(map[int64]string)
// 读写锁保护缓存
rwLock sync.RWMutex
)
// 读操作:加读锁(并发安全)
func getUserName(userID int64) (string, bool) {
rwLock.RLock()
defer rwLock.RUnlock()
// 模拟读耗时(实际场景可能是复杂计算/缓存读取)
time.Sleep(10 * time.Millisecond)
name, ok := userCache[userID]
return name, ok
}
// 写操作:加写锁(独占)
func setUserName(userID int64, name string) {
rwLock.Lock()
defer rwLock.Unlock()
// 模拟写耗时(实际场景可能是数据库写入)
time.Sleep(100 * time.Millisecond)
userCache[userID] = name
}
func main() {
var wg sync.WaitGroup
// 100个读协程(高并发读)
wg.Add(100)
for i := 0; i < 100; i++ {
go func(id int64) {
defer wg.Done()
name, ok := getUserName(id % 10)
if ok {
fmt.Printf("读取用户%d:%s\n", id%10, name)
}
}(int64(i))
}
// 2个写协程(低频写)
wg.Add(2)
go func() {
defer wg.Done()
setUserName(1, "张三")
}()
go func() {
defer wg.Done()
setUserName(2, "李四")
}()
wg.Wait()
fmt.Println("最终缓存:", userCache)
}
性能对比
- 纯读场景:RWMutex 的 QPS 是 Mutex 的 10 倍以上;
- 读写混合(读:写 = 10:1):RWMutex 性能约为 Mutex 的 5 倍。
3. sync.WaitGroup:协程等待组
核心作用
等待一组协程全部完成,实现协程间的同步(替代 sleep 等待的不优雅写法)。
关键方法
Add(n int):设置需要等待的协程数量;Done():协程完成后调用,数量减 1;Wait():阻塞当前协程,直到等待数量为 0。
实战示例:批量执行任务
package main
import (
"fmt"
"sync"
)
// 模拟业务任务
func task(id int, wg *sync.WaitGroup) {
defer wg.Done() // 协程退出前必须调用
fmt.Printf("任务%d开始执行\n", id)
// 模拟任务耗时
for i := 0; i < 100000000; i++ {}
fmt.Printf("任务%d执行完成\n", id)
}
func main() {
var wg sync.WaitGroup
taskNum := 5
wg.Add(taskNum) // 等待5个协程
for i := 0; i < taskNum; i++ {
go task(i, &wg)
}
wg.Wait() // 阻塞直到所有任务完成
fmt.Println("所有任务执行完毕")
}
避坑要点
Add()必须在启动协程前调用:避免协程已完成但Add()未执行,导致Wait()提前返回;Done()必须与Add()数量匹配:否则会 panic(Add (5) 但只调用 4 次 Done ())。
4. sync.Once:一次性执行
核心作用
保证某个函数仅执行一次,即使多协程同时调用(典型场景:单例初始化)。
关键方法
Do(f func()):传入需单次执行的函数,底层通过原子操作实现,无锁且高效。
实战示例:单例配置初始化
package main
import (
"fmt"
"sync"
)
// 全局配置单例
type AppConfig struct {
Port int
AppName string
}
var (
config *AppConfig
once sync.Once
)
// 初始化配置(仅执行一次)
func initConfig() {
fmt.Println("执行配置初始化...")
config = &AppConfig{
Port: 8080,
AppName: "sync-demo",
}
}
// 获取配置单例
func GetConfig() *AppConfig {
once.Do(initConfig) // 多协程调用仅执行一次initConfig
return config
}
func main() {
var wg sync.WaitGroup
wg.Add(10)
// 10个协程同时获取配置
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
cfg := GetConfig()
fmt.Printf("协程获取配置:%+v\n", cfg)
}()
}
wg.Wait()
}
适用场景
- 配置文件加载、数据库连接初始化;
- 单例模式实现、全局资源的一次性初始化。
5. sync.Map:并发安全的 Map
核心作用
替代 Go 原生 map(非并发安全),专为 "读多写少" 场景优化的并发安全 Map。
关键方法
Load(key interface{}) (value interface{}, ok bool):读取值;Store(key, value interface{}):写入值;Delete(key interface{}):删除值;Range(f func(key, value interface{}) bool):遍历所有键值对。
实战示例:全局并发缓存
package main
import (
"fmt"
"sync"
)
func main() {
var cache sync.Map
var wg sync.WaitGroup
// 10个协程写入数据
wg.Add(10)
for i := 0; i < 10; i++ {
go func(id int) {
defer wg.Done()
cache.Store(id, fmt.Sprintf("value_%d", id))
}(i)
}
// 10个协程读取数据
wg.Add(10)
for i := 0; i < 10; i++ {
go func(id int) {
defer wg.Done()
if val, ok := cache.Load(id); ok {
fmt.Printf("读取key=%d,value=%s\n", id, val)
}
}(i)
}
wg.Wait()
// 遍历所有数据
fmt.Println("遍历缓存:")
cache.Range(func(key, value interface{}) bool {
fmt.Printf("key=%v, value=%v\n", key, value)
return true // 返回true继续遍历,false终止
})
}
性能对比
- 读多写少场景:sync.Map 性能优于
map+RWMutex; - 写多场景:建议用
map+Mutex(sync.Map 的写操作有额外开销)。
6. sync.Pool:临时对象池
核心作用
复用临时对象,减少 GC(垃圾回收)压力,提升高频创建 / 销毁对象场景的性能。
关键方法
Get() interface{}:从池中获取对象(无则调用 New 创建);Put(x interface{}):将对象放回池中;New func() interface{}:池为空时创建新对象的函数。
实战示例:复用字节缓冲区
package main
import (
"bytes"
"fmt"
"sync"
)
// 创建字节缓冲区池
var bufPool = sync.Pool{
New: func() interface{} {
// 新建缓冲区,初始容量1024
return &bytes.Buffer{}
},
}
// 模拟高频创建缓冲区的函数
func processData(data string) string {
// 从池获取缓冲区
buf := bufPool.Get().(*bytes.Buffer)
// 用完放回池(清空内容)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()
// 模拟数据处理
buf.WriteString("processed: ")
buf.WriteString(data)
return buf.String()
}
func main() {
var wg sync.WaitGroup
wg.Add(1000)
// 1000个协程并发处理数据
for i := 0; i < 1000; i++ {
go func(id int) {
defer wg.Done()
result := processData(fmt.Sprintf("data_%d", id))
fmt.Printf("处理结果:%s\n", result)
}(i)
}
wg.Wait()
fmt.Println("所有数据处理完成")
}
适用场景
- 高频创建的临时对象(如字节缓冲区、连接对象、结构体实例);
- 注意:Pool 中的对象可能被 GC 回收,不能存储需持久化的数据。
7. sync.Cond:条件变量
核心作用
实现协程间的 "等待 - 唤醒" 机制(典型场景:生产者 - 消费者模型)。
关键方法
Wait():释放锁并阻塞,直到被唤醒;Signal():唤醒一个等待的协程;Broadcast():唤醒所有等待的协程;L Locker:绑定的互斥锁(必须传入 Mutex/RWMutex)。
实战示例:生产者 - 消费者模型
package main
import (
"fmt"
"sync"
"time"
)
var (
queue []int // 消息队列
cond *sync.Cond // 条件变量
maxSize = 5 // 队列最大容量
)
// 生产者
func producer(id int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 3; i++ {
cond.L.Lock() // 加锁
// 队列满则等待
for len(queue) >= maxSize {
cond.Wait() // 释放锁并阻塞
}
// 生产数据
data := id*10 + i
queue = append(queue, data)
fmt.Printf("生产者%d生产:%d,队列长度:%d\n", id, data, len(queue))
cond.L.Unlock() // 解锁
cond.Signal() // 唤醒一个消费者
time.Sleep(100 * time.Millisecond)
}
}
// 消费者
func consumer(id int, wg *sync.WaitGroup) {
defer wg.Done()
for {
cond.L.Lock()
// 队列空则等待
for len(queue) == 0 {
cond.Wait()
}
// 消费数据
data := queue[0]
queue = queue[1:]
fmt.Printf("消费者%d消费:%d,队列长度:%d\n", id, data, len(queue))
cond.L.Unlock()
cond.Signal() // 唤醒一个生产者
time.Sleep(200 * time.Millisecond)
}
}
func main() {
// 初始化条件变量(绑定互斥锁)
cond = sync.NewCond(&sync.Mutex{})
var wg sync.WaitGroup
// 启动2个生产者、3个消费者
wg.Add(2)
go producer(1, &wg)
go producer(2, &wg)
wg.Add(3)
go consumer(1, &wg)
go consumer(2, &wg)
go consumer(3, &wg)
wg.Wait()
}
避坑要点
Wait()必须在循环中调用:防止虚假唤醒(协程被唤醒后,条件可能仍不满足);- 调用
Wait()前必须先加锁:否则会 panic。
8. sync/atomic:原子操作(附属子包)
核心作用
无锁实现简单数值的原子性操作(如计数器、标志位),性能优于 Mutex。
常用方法
atomic.AddInt64(&v, 1):原子自增;atomic.StoreInt64(&v, 10):原子赋值;atomic.LoadInt64(&v):原子读取;atomic.CompareAndSwapInt64(&v, old, new):CAS 操作。
实战示例:原子计数器
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var count int64 // 原子操作必须用int64/uint64等类型
func add(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
atomic.AddInt64(&count, 1) // 原子自增(无锁)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go add(&wg)
}
wg.Wait()
fmt.Printf("最终计数:%d(预期10000)\n", count)
}
适用场景
- 简单数值的原子操作(计数器、标志位);
- 复杂操作(如结构体修改)仍需用 Mutex。
三、sync 库使用原则
- 最小锁粒度:仅对临界区加锁,避免锁包裹整个函数;
- 优先原子操作:简单数值操作用 atomic,而非 Mutex;
- 读写分离:多读少写用 RWMutex,纯写用 Mutex;
- 避免死锁 :
- 不要在加锁后调用阻塞函数(如 IO、sleep);
- 避免协程间交叉加锁;
- Pool 不存持久数据:Pool 中的对象可能被 GC 回收,仅用于临时对象复用。
四、总结
sync库是 Go 进程内并发编程的核心,不同组件适配不同场景:
- 共享资源保护:Mutex(通用)、RWMutex(多读少写)、atomic(简单数值);
- 协程同步:WaitGroup(批量等待)、Cond(条件唤醒);
- 资源复用:sync.Map(并发 Map)、Pool(临时对象);
- 单次执行:Once(单例初始化)。