sync.RWMutex 读写锁一、读写锁解决了什么问题普通的 Mutex 不管操作是读还是写一律互斥。但很多场景下读操作远多于写操作读操作之间本可以安全并发——它们不会修改数据。RWMutex 提供两种锁读锁RLock多个 goroutine 可以同时持有彼此不阻塞写锁Lock独占持有期间所有读锁和写锁都被阻塞这一设计显著提升了读多写少场景的并发性能。二、基本用法var(mu sync.RWMutex balanceint64)// 读操作使用读锁funcBalance()int64{mu.RLock()defermu.RUnlock()returnbalance}// 写操作使用写锁和 Mutex 一样funcDeposit(amountint64){mu.Lock()defermu.Unlock()balanceamount}命名约定Lock/Unlock 用于写RLock/RUnlock 用于读。三、锁的互斥规则已有读锁已有写锁请求读锁✅ 允许❌ 阻塞请求写锁❌ 阻塞❌ 阻塞核心理解读读不互斥其他都互斥。四、适用场景判断RWMutex 不是万能的。它的内部维护了更复杂的记账信息无竞争时比 Mutex 稍慢。只有在以下条件同时满足时RWMutex 才有优势读操作占绝大多数存在锁竞争多个 goroutine 同时在等锁如果没有竞争Mutex 更轻更快如果写操作频繁RWMutex 不但无益还会因为写锁不断阻塞读锁而导致性能下降。五、读锁不能升级为写锁这是一个经典的坑你不能先 RLock然后在持有读锁的情况下再 Lock。mu.RLock()// ... 发现需要修改数据 ...mu.Lock()// 死锁写锁在等待读锁释放读锁永远不会释放正确的做法是先 RUnlock再 Lock并且重新检查条件因为释放读锁到获取写锁之间数据可能已被其他 goroutine 改变mu.RLock()ifneedUpdate{mu.RUnlock()mu.Lock()// 重新检查可能在等写锁期间别人已经改过了ifneedUpdate{/* 执行更新 */}mu.Unlock()}else{mu.RUnlock()}六、不要假设只读就一定安全即使一个方法看起来只读取数据它也可能悄悄修改内部状态比如更新访问计数器、填充缓存。如果不确定用 Mutex 更安全。七、练习代码保存为rwmutex_demo.gopackagemainimport(fmtsynctime)// 一个简单的配置中心读多写少typeConfigstruct{mu sync.RWMutex datamap[string]string}funcNewConfig()*Config{returnConfig{data:map[string]string{version:1.0,env:dev},}}// 读配置允许并发读func(c*Config)Get(keystring)string{c.mu.RLock()deferc.mu.RUnlock()returnc.data[key]}// 写配置独占func(c*Config)Set(key,valuestring){c.mu.Lock()deferc.mu.Unlock()c.data[key]value}// 升级演示先读后决定是否写func(c*Config)SetIfChanged(key,newValuestring)bool{c.mu.RLock()oldValue:c.data[key]c.mu.RUnlock()ifoldValuenewValue{returnfalse// 值相同不写}// 获取写锁重新检查c.mu.Lock()deferc.mu.Unlock()// 重新检查等写锁期间可能已被别人修改ifc.data[key]newValue{returnfalse}c.data[key]newValuereturntrue}funcmain(){cfg:NewConfig()varwg sync.WaitGroup// 模拟 100 个并发读fori:0;i100;i{wg.Add(1)gofunc(idint){deferwg.Done()v:cfg.Get(version)_v// 实际使用}(i)}// 模拟 5 个并发写fori:0;i5;i{wg.Add(1)gofunc(idint){deferwg.Done()cfg.Set(fmt.Sprintf(key_%d,id),fmt.Sprintf(val_%d,id))}(i)}// 模拟升级锁模式fori:0;i10;i{wg.Add(1)gofunc(idint){deferwg.Done()cfg.SetIfChanged(version,1.0)}(i)}wg.Wait()c.mu.RLock()fmt.Printf(配置条目数: %d\n,len(c.data))fmt.Printf(version: %s\n,c.data[version])c.mu.RUnlock()}运行结果配置条目数: 7 version: 1.0八、关键要点要点说明RLock/RUnlock读锁允许多个 goroutine 同时持有Lock/Unlock写锁独占阻塞所有读写不可升级不能从读锁升级为写锁必须先释放再获取重新检查释放读锁到获取写锁之间数据可能变化需 double-check适用场景读多写少 存在锁竞争无竞争时Mutex 比 RWMutex 更轻更快