拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Go语言第十三章(互斥锁,读写锁)

互斥锁与读写锁上一篇用channel在协程之间传数据。有时协程要共改同一块内存——比如几个协程一起往同一个切片里append。这时通道不是唯一答案标准库sync里的锁也很常用。入门先记住三件事互斥锁Mutex同一时刻只允许一个协程进「临界区」改共享数据。不加锁并发改切片 / map可能丢数据、甚至崩溃数据竞争。读写锁RWMutex读可以多人同时读写还是独占。适合「读多写少」。口诀对照channel 偏「把结果递出去」锁偏「大家轮流改同一份」。为什么要锁奶茶菜单被同时加料场景菜单里已有三杯奶茶三个协程各自再加一杯。共享的是同一个切片arr改它时要排队。加了互斥锁每次都能累加成功packagemainimport(fmtsync)funcworker(arr*[]string,wg*sync.WaitGroup,mu*sync.Mutex,teastring){deferwg.Done()mu.Lock()// 上锁别人先别动 arr*arrappend(*arr,tea)// 临界区只有拿到锁的协程能改mu.Unlock()// 解锁下一位可以进来}funcmain(){varwg sync.WaitGroupvarmu sync.Mutex arr:[]string{芋泥波波,绿茶,红茶}wg.Add(3)goworker(arr,wg,mu,柠檬茶)goworker(arr,wg,mu,霸王茶姬)goworker(arr,wg,mu,爷爷不泡茶)wg.Wait()fmt.Println(arr)}多次运行切片里都会有6 个名字原来 3 个 新加 3 个。顺序可能每次不同谁先抢到锁谁先加但不会少[芋泥波波 绿茶 红茶 柠檬茶 霸王茶姬 爷爷不泡茶]或[芋泥波波 绿茶 红茶 霸王茶姬 柠檬茶 爷爷不泡茶]要点写法含义mu.Lock()拿到锁别人正在锁里时这里会卡住等待mu.Unlock()放开锁漏写会让别的协程永远等*arr传的是切片指针改的才是main里那份wg/mu也要传指针和上一篇 WaitGroup 一样传值等于改副本外面感知不到defer wg.Done()放在函数开头保证协程怎么退出都会报到锁则建议尽快 Unlock不要把无关的慢操作包在锁里。不加锁会怎样看起来像「被盖掉」把Lock/Unlock拿掉三个协程同时append同一份切片funcworker(arr*[]string,wg*sync.WaitGroup,teastring){deferwg.Done()*arrappend(*arr,tea)// 没有锁多人同时改}append内部要读长度、扩容、写元素——几步叠在一起。多协程交错执行时可能出现有的茶没加进去少了几杯像被覆盖偶发panic例如索引越界用go run -race会报data race所以入门结论很简单多人同时改同一份切片 / map先加锁或改成只用 channel 串行化。读写锁读多写少时更合适Mutex读写都互斥有人读菜单时别人也不能读。若业务是「很多人都在看菜单偶尔改一次」可以用sync.RWMutexRLock/RUnlock加读锁多个读可以同时进行。Lock/Unlock加写锁写的时候别人既不能读也不能写。下面继续用菜单两个协程读、一个协程写。写这边故意Sleep(1ms)先让读锁先拿到方便体会「读可以并行、写要等读完」。packagemainimport(fmtsynctime)funcread(arr*[]string,wg*sync.WaitGroup,mu*sync.RWMutex){deferwg.Done()mu.RLock()fmt.Println(*arr)time.Sleep(20*time.Millisecond)// 模拟慢慢读mu.RUnlock()}funcwrite(arr*[]string,wg*sync.WaitGroup,mu*sync.RWMutex,teastring){deferwg.Done()time.Sleep(1*time.Millisecond)// 先让读锁触发mu.Lock()time.Sleep(10*time.Millisecond)// 模拟写入耗时*arrappend(*arr,tea)mu.Unlock()}funcmain(){varwg sync.WaitGroupvarrwmu sync.RWMutex menu:[]string{芋泥波波,绿茶,红茶}wg.Add(3)goread(menu,wg,rwmu)goread(menu,wg,rwmu)gowrite(menu,wg,rwmu,爷爷不泡茶)wg.Wait()fmt.Println(最终菜单,menu)}可能的输出两个读可以重叠所以往往先看到两次「还没加料」的菜单写在读锁释放后才能完成[芋泥波波 绿茶 红茶] [芋泥波波 绿茶 红茶] 最终菜单 [芋泥波波 绿茶 红茶 爷爷不泡茶]要点写法含义mu.RLock()/RUnlock()读锁多个read可以同时拿着mu.Lock()/Unlock()写锁要等读锁都放开且期间别人读不了、也写不了write里先Sleep(1ms)演示用让读先执行真实代码一般不需要这样凑时序记住只读 →RLock/RUnlock要改 → 还是Lock/Unlock写锁入门阶段不确定就用Mutex确认读远多于写再换成RWMutex和 channel 怎么选入门版场景建议协程算完把结果交回channel多个结果依次汇总、或流水线channel多个协程共改同一个切片 / map / 计数器Mutex或RWMutex读很多、写很少的共享数据优先考虑RWMutex只是等任务做完、不改共享数据继续用WaitGroup即可Go 社区常说「不要通过共享内存来通信而要通过通信来共享内存」——意思是优先 channel。但计数器、缓存、就地改结构体字段这类场景加锁往往更直接。两种都会用按场景选。作业复现丢数据用本文「不加锁」版本跑十几次观察切片长度是否总是 6有条件的话用go run -race .看是否报 data race。加上 Mutex恢复Lock/Unlock确认每次长度都是 6并打印出完整菜单。计数器多个协程给同一个var n int各加 1000 次。分别写「无锁」和「Mutex保护n」两版对比最终n是否等于期望值。读写锁小练习一个map[string]int表示库存。启动 3 个协程用RLock打印某商品库存再启动 1 个协程用写锁把该商品库存1。用WaitGroup等全部结束。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门