尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Go-sync包并发原语详解从Mutex到Once的深度应用

Go-sync包并发原语详解从Mutex到Once的深度应用 Go sync包并发原语详解从Mutex到Once的深度应用文章导语Go的并发哲学是不要通过共享内存来通信而要通过通信来共享内存。但现实世界中共享内存仍然是不可避免的。sync包提供了Mutex、RWMutex、WaitGroup、Once、Cond、Pool等一系列并发原语掌握它们是写出正确并发程序的基石。一、Mutex互斥锁的底层原理1.1 Mutex的三种模式// runtime/sync/mutex.gotypeMutexstruct{stateint32// 锁状态semauint32// 信号量}state字段包含四个信息Waiter29位等待者的数量Starving1位是否处于饥饿模式Woken1位是否有goroutine被唤醒Locked1位是否已锁定1.2 正常模式 vs 饥饿模式// 正常模式FIFO等待队列新到达的goroutine尝试自旋获取锁// 饥饿模式当等待者等待超过1ms时触发直接释放给队首等待者自旋条件运行在多核CPU上GOMAXPROCS 1至少存在一个正在运行的P当前P的本地运行队列为空// 自旋的伪代码fori:0;ispin;i{forstateLocked0{ifatomic.CAS(m.state,state,state|Locked){return// 获取到锁}}procyield(30)// CPU空转}二、RWMutex读写锁的正确使用// RWMutex的场景化选择typeCachestruct{mu sync.RWMutex itemsmap[string]interface{}}// 读——用RLockfunc(c*Cache)Get(keystring)interface{}{c.mu.RLock()deferc.mu.RUnlock()returnc.items[key]}// 写——用Lockfunc(c*Cache)Set(keystring,valinterface{}){c.mu.Lock()deferc.mu.Unlock()c.items[key]val}写优先机制当有写锁等待时新来的读锁需要排队等待防止写锁饥饿。三、WaitGroup协程同步// 经典使用模式funcprocessConcurrently(items[]Item){varwg sync.WaitGroupfor_,item:rangeitems{wg.Add(1)item:item// Go 1.22前必须gofunc(){deferwg.Done()process(item)}()}wg.Wait()}// 常见错误Add在goroutine内部funcwrong(){varwg sync.WaitGroupfori:0;i10;i{gofunc(){wg.Add(1)// 错误可能在Wait()之后执行deferwg.Done()doWork()}()}wg.Wait()}四、sync.Once单次执行// 线程安全的单例模式typesingletonstruct{}var(instance*singleton once sync.Once)funcGetInstance()*singleton{once.Do(func(){instancesingleton{}})returninstance}内部实现func(o*Once)Do(ffunc()){ifatomic.LoadUint32(o.done)0{o.doSlow(f)}}func(o*Once)doSlow(ffunc()){o.m.Lock()defero.m.Unlock()ifo.done0{// 双重检查deferatomic.StoreUint32(o.done,1)f()}}五、sync.Pool对象池// 正确使用sync.Pool减少GC压力varbufferPoolsync.Pool{New:func()interface{}{returnnew(bytes.Buffer)},}funcprocessRequest(datastring)string{buf:bufferPool.Get().(*bytes.Buffer)deferfunc(){buf.Reset()bufferPool.Put(buf)}()buf.WriteString(data)// 处理...returnbuf.String()}// 注意Pool中的对象可能被随时清理GC时// 不要依赖Put()的对象一定能被Get()到六、sync.Cond条件变量// 生产者-消费者模式有限队列typeQueuestruct{cond*sync.Cond items[]intmaxSizeint}func(q*Queue)Put(itemint){q.cond.L.Lock()deferq.cond.L.Unlock()forlen(q.items)q.maxSize{q.cond.Wait()// 等待消费者通知}q.itemsappend(q.items,item)q.cond.Signal()// 通知等待的消费者}func(q*Queue)Get()int{q.cond.L.Lock()deferq.cond.L.Unlock()forlen(q.items)0{q.cond.Wait()}item:q.items[0]q.itemsq.items[1:]q.cond.Signal()returnitem}七、全文总结原语使用场景注意事项Mutex互斥访问共享资源不可重入RWMutex读多写少写优先WaitGroupgoroutine同步Add在go之前Once单次初始化不可撤销Pool临时对象复用GC可能清理Cond条件等待必须配合Lock八、技术进阶展望sync/atomic的低级原子操作errgroup的并发错误处理semaphore的信号量模式参考文献Go sync包文档: https://pkg.go.dev/syncGo内存模型 - The Go Memory ModelGo源码 runtime/sema.go《Go并发编程实战》- 汪明Bryan Mills - Rethinking Classical Concurrency Patterns
返回列表