当前位置: 首页 > news >正文

实用指南:golang 剖析 sync包

实用指南:golang 剖析 sync包

目录

  • 简介
  • Mutex
  • RWMutex
  • WaitGroup
  • Once
  • Cond
  • Map
  • Pool

简介

  • golang的sync包提供了一些并发控制的工具,在应用程序开发过程中是非常有用的,下面详细介绍下这些工具的原理和使用
  • 在介绍工具之前,先讲解下内存模型中的happens-before关系
// 示例:Happens-Before关系
var a string
var done bool
func setup() {
a = "hello, world"  // 写操作A
done = true         // 写操作B
}
func main() {
go setup()
for !done {        // 读操作C
// 忙等待
}
print(a)           // 读操作D
}

关键点

  • 如果B happens-before C,那么A happens-before D
  • sync包的作用就是建立这种happens-before关系

Mutex

  • golang提供的锁,常用于保护共享资源的访问安全,使用如下
func main() {
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
mu.TryLock()
}
  • mu.Lock():加锁
  • mu.Unlock():解锁
  • mu.TryLock():尝试加锁,如果成功会返回true

锁结构如下

type Mutex struct {
state int32  // 锁状态:包含多个标志位
sema  uint32 // 信号量:用于阻塞goroutine
}
  • 加锁的时候,会先使用cas原子操作获取锁(CPU实现),如果拿不到说明已经被其他goroutine占有,通过自旋阻塞当前goroutine(这过程会通过PAUSE指令减少CPU功耗)
func (m *Mutex) Lock() {
// 快速路径:尝试原子操作获取锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
// 慢速路径:锁已被持有,需要等待
m.lockSlow()
}
  • 读写均衡或者写者较多的场景,使用这个锁(防止写者竞争,内存占用也更小)

RWMutex

  • golang提供的读写锁,和上面的锁区别是区分了读和写两种场景。读之间的冲突不阻塞,若写锁遇到了读锁,需要等待所有读者释放,若读者和写者同时到达,读者要等待写者完成(写优先)
func (rw *RWMutex) Lock() {
// 1. 获取互斥锁
rw.w.Lock()
// 2. 设置readerCount为负值,阻止新读者
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders)
// 3. 等待现有读者
if r != 0 {
runtime_Semacquire(&rw.writerSem)
}
}
func (rw *RWMutex) RLock() {
// 检查是否有写者(readerCount < 0表示有写者)
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// 有写者在等待或正在写,读者必须阻塞
runtime_Semacquire(&rw.readerSem)
}
}
  • 读多写少的场景使用这个读写锁,性能会有比较大的提升

WaitGroup

  • waitGroup提供了下面三个方法,用来控制多个goroutine是否都执行完成,主goroutine调用Add方法设置要等待的goroutine数量,每个goroutine结束的时候,调用Done方法标记这个任务结束,Wait方法会等待所有goroutine调用完Done方法
func main() {
var wg sync.WaitGroup
wg.Add(1) // 增加一个等待者
go func() {
defer wg.Done()
// 执行逻辑, 逻辑执行完之后, 等待者数量-1
}()
wg.Wait() // 等待所有等待者执行完成
}
  • 底层实现如下,它使用了一个state来存储等待者和要执行的工作数量
type WaitGroup struct {
noCopy noCopy
// 64位值的高32位是计数器,低32位是等待者数量
// 64位原子操作需要64位对齐,但32位编译器不能确保这一点
state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
}
// WaitGroup状态布局(64位)
// 高32位:计数器 (counter)
// 低32位:等待者数量 (waiters)
func (wg *WaitGroup) Add(delta int) {
statep, semap := wg.state()
// 更新计数器
state := atomic.AddUint64(statep, uint64(delta)<<32)
// 检查状态变化
v := int32(state >> 32)  // 计数器
w := uint32(state)       // 等待者数量
if v > 0 || w == 0 {
return // 还有工作要做,或者没有等待者
}
// 所有工作完成,唤醒等待者
if *statep != 0 {
panic("sync: WaitGroup misuse")
}
// 唤醒所有等待的goroutine
for ; w != 0; w-- {
runtime_Semrelease(semap, false, 0)
}
}

具体

  1. 高32位 = 工作计数器
    wg.Add(5) // 高32位 += 5,表示还有5个工作要做
    wg.Done() // 高32位 -= 1,表示完成了1个工作
  • 当高32位变为0时,表示所有工作完成
  1. 低32位 = 等待者数量
    wg.Wait() // 低32位 += 1,表示1个goroutine开始等待
  • 当高32位变为0时,低32位表示需要唤醒多少个goroutine

当调用Wait方法时,也是一个自旋等待的逻辑。配合cas,实现等待所有工作完成。具体细节参考源码,这里只简单介绍下原理

Once

  • sync.Once{}提供了一个多次调用,只执行一次的方法实现,如下所示
func main() {
once := sync.Once{}
once.Do(func() {
// 这里的逻辑只会执行一次
})
}
  • 这个的底层实现比较简单,因为once内部维护了一个原子类,指向一个32位无符号数,当执行func的时候,如果有多个goroutine并发执行,两个goroutine会竞争锁,最终只有一个能够执行func的逻辑,执行完之后,这个数会设置为1,从而后面的所有goroutine都不会再走到执行逻辑。具体参考下面源码实现
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}

Cond

  • 这个工具主要是协调访问共享资源的那些goroutinec.L是工具内部的一个锁。当调用c.L.Wait()会解锁等待Signal或者Broadcast信号,Broadcast会唤醒所有等待的WaitSignal会随机唤醒一个等待的Wait。具体使用方法如下面的例子所示
func main() {
cond := sync.NewCond(new(sync.Mutex))
done := false
read := func(name string, c *sync.Cond) {
c.L.Lock()
if !done {
c.Wait()
}
fmt.Println("start reading: ", name)
c.L.Unlock()
}
write := func(name string, c *sync.Cond) {
fmt.Println("start writing: ", name)
c.L.Lock()
done = true
c.L.Unlock()
fmt.Println("wakes all:", name)
c.Signal()
}
go read("read3", cond)
go read("read1", cond)
go read("read2", cond)
write("write1", cond)
time.Sleep(5 * time.Second)
}

Map

  • golang在sync包内提供了一个并发安全的Map,可以增删改查,遍历、compareAndSwap、LoadOrStore等操作。可以做一些无锁编程,提升性能
  • 读多写少(读操作占比>80%)、数据量大(键值对数量>1000)、写入不频繁(写入频率<100次/秒)、简单键值等场景,使用这个的性能要优于普通map+锁

Pool

// 频繁分配和回收的典型特征
func frequentAllocation() {
pool := sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
// 在循环中重复创建和销毁对象
for i := 0; i < 1000; i++ {
// 分配:创建新对象
// buf := make([]byte, 1024)  // 直接分配内存
buf := pool.Get().([]byte) // 使用对象池
// 使用对象
copy(buf, data[i])
process(buf)
// 回收:对象超出作用域,被GC回收
pool.Put(buf[:0]) // 归还对象(清空)
}
// 每次循环都经历:分配→使用→回收
}
http://www.jsqmd.com/news/365870/

相关文章:

  • 河北地区高性价比斜弱视干预公司怎么选择,这里有指引 - 工业推荐榜
  • 聊聊直进式拉丝机资深厂商排名及性价比情况 - myqiye
  • 聊聊2026年口碑好的石墨烯厂家,烯牛实业优势显著 - 工业设备
  • 2026年调直机精密定制费用排名,合肥、福州地区靠谱商家盘点 - 工业品牌热点
  • 实用指南:学习go语言
  • 模型、框架、应用量产工作流,原力灵机三箭齐发,开启具身智能元年
  • 2026年上海地区精密间歇分割器品牌排名,技术先进的品牌全解析 - 工业推荐榜
  • 导师又让重写?8个降AI率网站深度测评与推荐
  • canoe capl写的uds基础代码脚本测试用例 1、10服务和22服务测试用例,拿去参考...
  • 2026年全国杀菌剂厂家权威榜单 智能精准防控 适配多作物场景 涵盖大田果蔬茶园 - 深度智识库
  • 什么是渗透测试?
  • 照着用就行:千笔AI,研究生论文写作救星
  • 甄选十大留学中介,南京本土资深团队定制申学突围方案 - 博客湾
  • 实验用猴材料全攻略:组织 / 全血 / 血清 / 血浆厂家推荐 - 品牌推荐大师
  • 2026 福州英语雅思培训教育机构推荐、雅思培训课程中心权威口碑榜单 - 老周说教育
  • 上海留学中介外籍导师团队:名校海归顾问配置哪家更全? - 博客湾
  • 2026 福州英语雅思培训教育机构推荐;雅思培训课程中心权威口碑榜单 - 老周说教育
  • 避坑指南!2026北京月嫂服务公司推荐排行 数字化管理+安全保障 - 极欧测评
  • 2026层流布气罩选型指南:四大核心场景适配品牌评估及采购决策框架 - 博客万
  • Vue3 计算属性详解及用法
  • 纯正的复合益生菌oem 2026年益生菌贴牌厂家怎么选?精选5大核心维度权威测评 - 博客万
  • 2026 珠海英语雅思培训教育机构推荐|雅思培训课程中心权威口碑榜单 - 老周说教育
  • 美国留学中介面试辅导:模拟演练哪家还原度更高? - 博客湾
  • 2026年高低温试验箱行业推荐报告:半导体/军工级需求下的优选品牌 - 博客万
  • 腾讯企业微信官方授权服务商和美字节简介
  • 上海留学中介国际学校服务:IB/A-Level体系申请谁家更懂? - 博客湾
  • 世界硒都藏好物! 鄂达生物云山金子山黄精,解锁千年养生新方式 - 博客万
  • 2026 石家庄英语雅思培训教育机构推荐:雅思培训课程中心权威口碑榜单 - 老周说教育
  • 2026 福州英语雅思培训教育机构推荐,雅思培训课程中心权威口碑榜单 - 老周说教育
  • 北京留学机构排名TOP10,量身打造方案提升录取率 - 博客湾