Go sync包并发原语详解

前言

Go的goroutine和channel解决了大部分并发问题,但有些场景下,sync包提供的原语更简洁高效。比如保护共享变量、等待一组goroutine完成、确保初始化只执行一次等。

本文整理sync包中常用类型的使用方法和注意事项,配合实际代码示例。


1. Mutex:互斥锁

最基础的锁,同一时刻只有一个goroutine能持有。

1.1 基本用法

packagemainimport("fmt""sync")typeCounterstruct{mu sync.Mutex valueint}func(c*Counter)Inc(){c.mu.Lock()deferc.mu.Unlock()c.value++}func(c*Counter)Value()int{c.mu.Lock()deferc.mu.Unlock()returnc.value}funcmain(){varwg sync.WaitGroup counter:=&Counter{}fori:=0;i<1000;i++{wg.Add(1)gofunc(){deferwg.Done()counter.Inc()}()}wg.Wait()fmt.Println("Final value:",counter.Value())// 1000}

1.2 常见错误

错误1:忘记Unlock

func(c*Counter)Inc(){c.mu.Lock()c.value++// 忘记 Unlock,其他goroutine会永远阻塞}

defer可以避免这个问题,即使函数panic也能正常解锁。

错误2:复制带锁的结构体

funcmain(){c1:=Counter{}c2:=c1// 错误:复制了mutex// c1和c2共享同一把锁的状态,行为不可预期}

解决方案:传递指针,或者使用noCopy模式。

typeCounterstruct{mu sync.Mutex valueint_noCopy// go vet会检查复制}typenoCopystruct{}func(*noCopy)Lock(){}func(*noCopy)Unlock(){}

错误3:重复Lock(死锁)

func(c*Counter)Double(){c.mu.Lock()deferc.mu.Unlock()c.Inc()// Inc里面也会Lock,死锁}

解决方案:拆分内部方法,不加锁的版本供内部调用。

func(c*Counter)inc(){c.value++// 不加锁,仅内部使用}func(c*Counter)Inc(){c.mu.Lock()deferc.mu.Unlock()c.inc()}func(c*Counter)Double(){c.mu.Lock()deferc.mu.Unlock()c.inc()c.inc()}

2. RWMutex:读写锁

读多写少的场景下,用读写锁比互斥锁性能好。

  • 多个goroutine可以同时持有读锁
  • 写锁是排他的,持有写锁时不能有其他读锁或写锁

2.1 基本用法

typeConfigstruct{mu sync.RWMutex datamap[string]string}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)GetAll()map[string]string{c.mu.RLock()deferc.mu.RUnlock()// 返回副本,防止外部修改result:=make(map[string]string,len(c.data))fork,v:=rangec.data{result[k]=v}returnresult}

2.2 性能对比

funcBenchmarkMutex(b*testing.B){varmu sync.Mutexvarvalueintb.RunParallel(func(pb*testing.PB){forpb.Next(){mu.Lock()_=value mu.Unlock()}})}funcBenchmarkRWMutex(b*testing.B){varmu sync.RWMutexvarvalueintb.RunParallel(func(pb*testing.PB){forpb.Next(){mu.RLock()_=value mu.RUnlock()}})}

读多写少时,RWMutex明显更快;但如果写操作频繁,RWMutex开销反而更大。

2.3 注意事项

  1. 读锁内不要调用写锁方法(会死锁)
  2. RWMutex是写优先的,有goroutine等待写锁时,后续读锁请求会阻塞
  3. 不要在热点路径滥用RWMutex,如果读写比例接近,用Mutex更简单

3. WaitGroup:等待一组goroutine

3.1 基本用法

funcmain(){varwg sync.WaitGroup urls:=[]string{"https://example.com","https://example.org","https://example.net",}for_,url:=rangeurls{wg.Add(1)gofunc(urlstring){deferwg.Done()resp,err:=http.Get(url)iferr!=nil{fmt.Println("Error:",err)return}deferresp.Body.Close()fmt.Println(url,resp.Status)}(url)}wg.Wait()fmt.Println("All done")}

3.2 常见错误

错误1:Add在goroutine内部调用

// 错误for_,url:=rangeurls{gofunc(urlstring){wg.Add(1)// 可能在Wait之后执行deferwg.Done()// ...}(url)}wg.Wait()// 可能提前返回

错误2:Done调用次数不匹配

wg.Add(1)gofunc(){ifsomeCondition{return// 忘记Done}wg.Done()}()

defer可以确保一定执行。

3.3 带超时的等待

WaitGroup本身不支持超时,可以配合channel实现:

funcwaitWithTimeout(wg*sync.WaitGroup,timeout time.Duration)bool{done:=make(chanstruct{})gofunc(){wg.Wait()close(done)}()select{case<-done:returntruecase<-time.After(timeout):returnfalse}}funcmain(){varwg sync.WaitGroup wg.Add(1)gofunc(){deferwg.Done()time.Sleep(2*time.Second)}()ifwaitWithTimeout(&wg,1*time.Second){fmt.Println("Completed")}else{fmt.Println("Timeout")}}

4. Once:确保只执行一次

4.1 典型场景:单例初始化

typeDatabasestruct{conn*sql.DB}var(dbInstance*Database dbOnce sync.Once)funcGetDB()*Database{dbOnce.Do(func(){conn,err:=sql.Open("mysql","dsn")iferr!=nil{panic(err)}dbInstance=&Database{conn:conn}})returndbInstance}

4.2 注意事项

Once.Do只执行一次,即使panic了也不会重试

varonce sync.Oncevarconfig*ConfigfuncGetConfig()*Config{once.Do(func(){data,err:=ioutil.ReadFile("config.json")iferr!=nil{panic(err)// panic后,once.Do不会再执行}json.Unmarshal(data,&config)})returnconfig// 如果上面panic了,这里返回nil}

如果需要重试,不能用sync.Once:

typeLazyConfigstruct{mu sync.Mutex config*Config}func(l*LazyConfig)Get()(*Config,error){l.mu.Lock()deferl.mu.Unlock()ifl.config!=nil{returnl.config,nil}// 可重试的初始化data,err:=ioutil.ReadFile("config.json")iferr!=nil{returnnil,err}varcfg Configiferr:=json.Unmarshal(data,&cfg);err!=nil{returnnil,err}l.config=&cfgreturnl.config,nil}

5. Cond:条件变量

用于goroutine间的信号通知,比channel更底层。

5.1 生产者消费者模式

typeQueuestruct{mu sync.Mutex cond*sync.Cond items[]int}funcNewQueue()*Queue{q:=&Queue{}q.cond=sync.NewCond(&q.mu)returnq}func(q*Queue)Put(itemint){q.mu.Lock()deferq.mu.Unlock()q.items=append(q.items,item)q.cond.Signal()// 通知一个等待的goroutine}func(q*Queue)Get()int{q.mu.Lock()deferq.mu.Unlock()forlen(q.items)==0{q.cond.Wait()// 释放锁并等待}item:=q.items[0]q.items=q.items[1:]returnitem}

5.2 Broadcast:通知所有等待者

typeBarrierstruct{mu sync.Mutex cond*sync.Cond countinttargetint}funcNewBarrier(nint)*Barrier{b:=&Barrier{target:n}b.cond=sync.NewCond(&b.mu)returnb}func(b*Barrier)Wait(){b.mu.Lock()deferb.mu.Unlock()b.count++ifb.count==b.target{b.cond.Broadcast()// 所有人到齐,通知全部return}forb.count<b.target{b.cond.Wait()}}

实际项目中,大部分场景用channel就够了,Cond用得比较少。


6. Pool:对象复用池

减少内存分配和GC压力。

6.1 基本用法

varbufferPool=sync.Pool{New:func()interface{}{returnnew(bytes.Buffer)},}funcprocess(data[]byte)string{buf:=bufferPool.Get().(*bytes.Buffer)deferfunc(){buf.Reset()bufferPool.Put(buf)}()buf.Write(data)// 处理...returnbuf.String()}

6.2 实际案例:JSON编码

varjsonEncoderPool=sync.Pool{New:func()interface{}{return&bytes.Buffer{}},}funcToJSON(vinterface{})([]byte,error){buf:=jsonEncoderPool.Get().(*bytes.Buffer)deferfunc(){buf.Reset()jsonEncoderPool.Put(buf)}()encoder:=json.NewEncoder(buf)iferr:=encoder.Encode(v);err!=nil{returnnil,err}// 返回副本,因为buf会被复用result:=make([]byte,buf.Len())copy(result,buf.Bytes())returnresult,nil}

6.3 注意事项

  1. Pool中的对象可能随时被回收(GC时),不要存储重要状态
  2. Get返回的对象可能是复用的,使用前要Reset
  3. Pool不是缓存,不保证对象一定存在
  4. 要确保Put回去的对象是干净的

7. Map:并发安全的map

7.1 基本用法

varcache sync.MapfuncGet(keystring)(interface{},bool){returncache.Load(key)}funcSet(keystring,valueinterface{}){cache.Store(key,value)}funcGetOrSet(keystring,valueinterface{})interface{}{actual,_:=cache.LoadOrStore(key,value)returnactual}funcDelete(keystring){cache.Delete(key)}funcRange(){cache.Range(func(key,valueinterface{})bool{fmt.Println(key,value)returntrue// 返回false停止遍历})}

7.2 适用场景

sync.Map针对以下两种场景优化:

  1. key只写一次但读很多次(缓存)
  2. 多个goroutine读写不同的key

其他场景下,用普通map+Mutex可能更好。

7.3 性能对比

// sync.MapfuncBenchmarkSyncMap(b*testing.B){varm sync.Map b.RunParallel(func(pb*testing.PB){i:=0forpb.Next(){m.Store(i,i)m.Load(i)i++}})}// map + MutexfuncBenchmarkMapMutex(b*testing.B){m:=make(map[int]int)varmu sync.Mutex b.RunParallel(func(pb*testing.PB){i:=0forpb.Next(){mu.Lock()m[i]=i mu.Unlock()mu.Lock()_=m[i]mu.Unlock()i++}})}

读多写少时sync.Map更快,写多时普通map+Mutex更好。


8. 原子操作:sync/atomic

比锁更轻量,适合简单的数值操作。

8.1 基本用法

import"sync/atomic"typeCounterstruct{valueint64}func(c*Counter)Inc(){atomic.AddInt64(&c.value,1)}func(c*Counter)Dec(){atomic.AddInt64(&c.value,-1)}func(c*Counter)Value()int64{returnatomic.LoadInt64(&c.value)}func(c*Counter)Reset(){atomic.StoreInt64(&c.value,0)}

8.2 CAS操作

func(c*Counter)CompareAndSwap(old,newint64)bool{returnatomic.CompareAndSwapInt64(&c.value,old,new)}// 无锁更新func(c*Counter)Update(fnfunc(int64)int64){for{old:=atomic.LoadInt64(&c.value)new:=fn(old)ifatomic.CompareAndSwapInt64(&c.value,old,new){return}}}

8.3 atomic.Value:存储任意类型

varconfig atomic.ValuefuncUpdateConfig(cfg*Config){config.Store(cfg)}funcGetConfig()*Config{returnconfig.Load().(*Config)}

注意:atomic.Value存储的类型必须一致,第一次Store什么类型,后续就只能Store相同类型。


总结

原语适用场景注意事项
Mutex保护共享变量用defer确保Unlock,不要复制
RWMutex读多写少写优先,读锁内不要写
WaitGroup等待一组goroutineAdd在goroutine外调用
Once单例初始化panic不会重试
Cond条件等待大部分场景用channel更好
Pool对象复用不是缓存,对象可能被回收
Map并发安全map只在特定场景有优势
atomic简单数值操作比锁更轻量

选择建议

  1. 能用channel就用channel,更符合Go的设计哲学
  2. 保护简单变量用Mutex,不要过度优化
  3. 读多写少考虑RWMutex,但要实测确认有收益
  4. 单例初始化用Once,简单可靠
  5. 热点路径的简单计数用atomic,避免锁竞争

这些并发原语各有适用场景,关键是理解其语义和限制,根据实际需求选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1119216.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

虎贲等考 AI:重新定义学术创作,全流程智能辅助工具

在学术探索的道路上&#xff0c;论文写作往往伴随着选题迷茫、文献繁杂、数据缺失、格式混乱等诸多难题。虎贲等考 AI 智能写作平台&#xff08;官网&#xff1a;https://www.aihbdk.com/&#xff09;应势而生&#xff0c;作为一款基于前沿人工智能技术打造的专业论文写作辅助工…

2026年诚信的郑州五金展,郑州工博会,郑州工业自动化及机器人展公司口碑推荐榜 - 品牌鉴赏师

引言在当今竞争激烈的会展行业中,如何准确评估一家展会公司的实力和口碑,为相关企业和从业者提供可靠的参考,成为了一个备受关注的问题。为此,我们联合国内权威的会展行业协会,依据其发布的《中国会展行业发展白皮…

课程论文还在 “熬夜凑字数”?虎贲等考 AI:3 小时搞定高分论文,逻辑深度双在线

对于大学生而言&#xff0c;课程论文是贯穿学业的 “常规任务”—— 既要贴合课程主题、满足学术规范&#xff0c;又要兼顾逻辑完整性与观点创新性&#xff0c;往往让学生陷入 “无从下笔” 的困境。有人东拼西凑导致重复率超标&#xff0c;有人框架混乱被导师打回&#xff0c;…

uni-app 上架 iOS 时常见的审核被拒原因有哪些?

uni-app 上架 iOS 常见审核被拒原因(附解决方法) 一、基础信息 / 合规类(最易踩坑) 1. 隐私政策不合规(拒审率最高)拒审原因:未填写隐私政策链接,或链接无法访问; 隐私政策内容空泛,未说明 App 收集的用户数…

西门子S7-300 PLC博途V14编程,甲醛生产线模拟量处理与电机控制,搭配博途WINCC上...

西门子S7-300PLC采用博途V14编程程序&#xff0c;附带博途WINCC上位机&#xff0c;项目内容甲醛生产线项目&#xff0c;模拟量处理&#xff0c;电机控制等。 含博途程序画面案例&#xff0c;PLC采用西门子300系列&#xff0c;画面组态软件采用博途WINCC,A13&#xff0c; 另有采…

真实业务场景死锁案例:电商订单处理

1. 业务场景介绍 场景&#xff1a;电商系统的订单确认流程&#xff0c;需要处理三个核心资源&#xff1a; 订单锁&#xff1a;防止同一订单被重复处理库存锁&#xff1a;防止库存超卖支付锁&#xff1a;防止重复支付 2. 死锁发生的真实代码 2.1 订单处理服务 Service publ…

深度学习毕设项目推荐-人工智能基于python深度学习的餐桌美食识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026年评价高的广州专利翻译,广州俄语翻译,广州法律翻译公司口碑推荐清单 - 品牌鉴赏师

引言在2026年,翻译服务行业在全球化的浪潮下愈发重要,尤其是在广州这样的国际化大都市,专利翻译、俄语翻译、法律翻译等专业领域的需求不断增长。为了给广大客户提供一份客观、公正、权威的广州翻译公司推荐清单,我…

【计算机毕业设计案例】基于python机器学习识别水果的成熟度

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026最新PLC控制柜企业top5推荐榜!优质生产厂家及服务商解析/选择指南 - 全局中转站

引言 随着工业4.0战略深入推进,工业自动化控制系统作为智能制造的核心枢纽,其稳定性与可靠性直接决定生产效率与工程安全。当前市场中,PLC控制柜产品同质化严重,设计不规范、工艺粗糙、售后响应滞后等问题频发,据…

2026年口碑好的IGBT散热器,CPU散热器,高密度散热器厂家选型参考手册 - 品牌鉴赏师

引言在当今科技飞速发展的时代,散热设备在各个领域的地位愈发关键,无论是通信行业的服务器芯片,还是新能源汽车的动力电池,都需要高效的散热解决方案以确保其稳定运行。为了给广大企业和消费者提供权威、可靠的散热…

学长亲荐!专科生必看TOP8AI论文写作软件测评

学长亲荐&#xff01;专科生必看TOP8AI论文写作软件测评 2026年专科生AI论文写作工具测评&#xff1a;为何值得一看&#xff1f; 随着人工智能技术的不断发展&#xff0c;越来越多的专科生开始借助AI工具提升论文写作效率。然而&#xff0c;面对市场上琳琅满目的AI写作软件&a…

吐血推荐专科生10款AI论文写作软件测评

吐血推荐专科生10款AI论文写作软件测评 2026年专科生必备的AI论文写作工具测评 在当前学术环境日益严格的背景下&#xff0c;专科生在撰写论文时面临诸多挑战&#xff0c;如选题困难、文献检索繁琐、格式规范不熟悉等。为帮助学生更高效地完成论文写作&#xff0c;笔者基于2026…

2026年热门的抛光粉,抛光粉汽车玻璃,不锈钢抛光粉厂家优质排行 - 品牌鉴赏师

引言在 2026 年的工业市场中,抛光粉作为一种关键的工业耗材,在汽车玻璃、不锈钢等多个领域发挥着至关重要的作用。为了给广大需求者提供一份客观、公正、真实的优质抛光粉厂家排行,我们联合国内相关行业协会,依据行…

LabVIEW 2025 安装图解:从下载到激活 一步到位

LabVIEW 2025 是 NI 推出的图形化编程工具,不用写代码,拖图标、连连线就能做测试测量、数据采集、工业控制类项目,适合工程师、科研人员和学生。 核心新亮点 1、调试更省心:断点、探针整合到一个窗口,数值直接显示…

2026年评价高的激光器水冷板,真空钎焊水冷板,搅拌摩擦焊水冷板厂家行业精选名录 - 品牌鉴赏师

引言在2026年的工业发展进程中,激光器水冷板、真空钎焊水冷板以及搅拌摩擦焊水冷板在众多领域发挥着至关重要的作用,其性能的优劣直接影响到相关设备的运行效率与稳定性。为了帮助广大用户在市场上众多的水冷板厂家中…

全域质量管理:数字化时代,如何让质量贯穿每一个环节?

在制造业竞争日益激烈的今天,质量已成为企业生存与发展的生命线。然而,传统的质量管理模式往往局限于“事后检验”,难以从根本上预防问题、提升品质。 随着数字化浪潮的推进,覆盖企业全流程、全角色、全数据的 “全…

当了3年产品经理,我终于把后端甩开了:一个 AIGC 应用的诞生当了3年产品经理,我终于把后端甩开了:一个 AIGC 应用的诞生

“这个需求很简单,怎么要排期一周?” 作为一名产品经理,这句话我不知在心里默念了多少遍。我每天都在创造、设计,但每一个想法从文档到现实,都隔着一道名为“研发资源”的墙。 我曾以为这是无法逾越的规则,直到我…

【计算机毕业设计案例】基于卷神经网络python深度学习的餐桌美食识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

计算机深度学习毕设实战-机器学习基于python卷积神经网络训练形状识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…