鞍山网站建设株洲网站建设

东莞市瑞域塑胶制品有限公司 2026/09/09 17:40:03

前言

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,避免锁竞争

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

seo网站建设建设行业网站

网络术语与Linux内核构建全解析1. 重要网络术语介绍在网络技术领域,有许多关键的术语和概念,它们构成了网络通信和数据处理的基础。下面将对一些重要的网络术语进行详细介绍。1.1 TCAM - 三元内

2026/06/30 10:59:23

门户网站建设方案网站建设套餐

8 个自考论文降重工具,AI 写作推荐与对比论文路上的“重担”:自考人的真实写照对于每一位自考学子来说,毕业论文从来不是一件轻松的事。它不仅是一次学术能力的考

2026/06/30 11:04:53

西安网站建设公司衡水网站建设

随着互联网的高速发展,运维安全已经成了大多数企业安全保障的基石。在如今的信息时代,无论是网络运维还是网络安全运维都成了不可缺少的一部分。因此导致很多人都容易把两者弄混淆。*

2026/06/30 12:26:31

市网站建设网站建设 英文

文章目录相关教程C#Python相关文献遍历 Entity获取 ModelSpace 中所有实体数量📌 补充说明🔁 如果你想统计整个数据库中的所有 Entity

2026/06/30 11:19:55

律师网站建设机票网站建设

2025年iOS微信红包插件深度评测:自动抢红包功能实测分析【免费下载链接】WeChatRedEnvelopesHelperiOS版微信抢红包插件,支持后台抢红包项目地址: https:

2026/06/30 10:34:20

上海 网站建设企业建设网站

阅读本文,你将了解——MES系统的核心职能,并学会如何正确判断一家生产型工厂是否需要MES。前言:在全民工业智能化时代,AI人工智能的兴起&#x

2026/06/30 13:11:04

南充网站建设随州网站建设

ComfyUI-Manager是专为ComfyUI设计的智能管理工具,彻底革新了传统手动安装插件的繁琐流程,让插件安装、更新和配置变得前所未有的简单高效。无论你是AI绘画爱

2026/06/30 11:21:55

牡丹江网站建设西宁网站建设

Go语言微服务如何集成TensorRT推理能力?在云原生AI应用快速落地的今天,一个常见但棘手的问题浮出水面:训练好的深度学习模型部署到生产环境后࿰

2026/06/30 11:07:54

襄樊网站建设网站建设与设计

智能体对话系统构建指南:以Anything-LLM为核心引擎在企业知识爆炸式增长的今天,员工花三小时翻找一份合同条款、客服反复查阅产品手册仍答非所问——这类场景早已司空见惯

2026/06/30 13:58:08

永州网站建设鄂州网站建设

form-generator与Vue3技术整合实战:构建高效表单开发方案【免费下载链接】form-generator:sparkles:Element UI表单设计及代码生成器项目地址:

2026/06/30 13:29:36