Golang sync.WaitGroup 源码分析
2021/11/27 9:11:13
本文主要是介绍Golang sync.WaitGroup 源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
结构
// WaitGroup类型的数据不可以被复制 type WaitGroup struct { noCopy noCopy // 用来禁止当前结构的类型复制 // state1 是 64-bit变量: // 高32位是计数器counter,也就是活跃的g的个数 // 低32位表示因执行Wait()而阻塞的g的数量,即waiters // state2 表示sema信号量,说明本章代码用到了原语 // 64-bit的原子操作需要64-bit的对齐,但是32位的编译器只能保证64-bit字段是32位对齐 // 因此,在32位架构上,我们需要在state()中检查state1是否对齐, // 并在需要时动态地 "交换 "字段顺序。 state1 uint64 state2 uint32 }
Add
Add
func (wg *WaitGroup) Add(delta int) { // 获取state1 和 state2 statep, semap := wg.state() // 竞争检测代码不看 if race.Enabled { _ = *statep // trigger nil deref early if delta < 0 { // Synchronize decrements with Wait. race.ReleaseMerge(unsafe.Pointer(wg)) } race.Disable() defer race.Enable() } // counter加delta state := atomic.AddUint64(statep, uint64(delta)<<32) v := int32(state >> 32) // 获取当前活跃的g的数量 w := uint32(state) // 获取当前Wait()的次数 // 竞争检测代码,不看 if race.Enabled && delta > 0 && v == int32(delta) { // The first increment must be synchronized with Wait. // Need to model this as a read, because there can be // several concurrent wg.counter transitions from 0. race.Read(unsafe.Pointer(semap)) } // 活跃的g个数不能是负数个,有可能delta传的是负数 if v < 0 { panic("sync: negative WaitGroup counter") } // 说明先调用的Wait()再调用Add(),正常应该反过来 if w != 0 && delta > 0 && v == int32(delta) { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // Add()执行成功返回 if v > 0 || w == 0 { return } // 到这一步说明 counter == 0 且 waiters > 0. // 如果*statep != state,有可能发生了两种错误情况 // - Add()和Wait()并发(concurrently)调用 // - 当counter归零时,waiters数量还在增加 // 继续检查,保证WaitGroup不被滥用 if *statep != state { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // 如果WaitGroup使用规范,到这一步counter为0说明无活跃g了 // 将state1置0,同时释放所有的waiters *statep = 0 for ; w != 0; w-- { runtime_Semrelease(semap, false, 0) } }
state
// 返回存state1和state2,即state和sema, // 因为32位编译器不能直接对齐64位数据,需要这个函数做对齐工作 func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 { //如果state1是64-bit则直接原样返回 return &wg.state1, &wg.state2 } else { // 如果state1是32-bit对齐而不是64-bit对齐, // 那么(&state1)+4 就是 64-bit 对齐了 state := (*[3]uint32)(unsafe.Pointer(&wg.state1)) return (*uint64)(unsafe.Pointer(&state[1])), &state[0] } }
-
unsafe.Alignof(T Type) 返回对齐方式,下表是32位系统和64位系统各大小数据的对齐方式值
32位系统:
Sizeof(x) = 16 Alignof(x) = 4 Sizeof(x.a) = 1 Alignof(x.a) = 1 Offsetof(x.a) = 0 Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2 Sizeof(x.c) = 12 Alignof(x.c) = 4 Offsetof(x.c) = 4
64位系统:
Sizeof(x) = 32 Alignof(x) = 8 Sizeof(x.a) = 1 Alignof(x.a) = 1 Offsetof(x.a) = 0 Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2 Sizeof(x.c) = 24 Alignof(x.c) = 8 Offsetof(x.c) = 8
-
unsafe.Pointer(&T) 是一个可以包含任意类型变量的地址的通用指针,所以unsafe.Pointer()常用于各种指针相互转换的桥梁。有四个特有的操作:
- 任何类型的指针都可以被转化为Pointer
- Pointer可以被转化为任何类型的指针
- uintptr可以被转化为Pointer
- Pointer可以被转化为uintptr
不能直接通过 *p 方式来取得真实的变量值,因为不知道变量的具体类型。可以通过 uintptr(unsafe.Pointer(&T)) 来进行运算。
unsafe.Pointer是可以比较的,并且支持和nil常量比较判断是否为空指针。
-
uintptr() 主要用来进行指针计算,本质是一个整型。一般用 uintptr(unsafe.Pointer(&T))来进行运算(T的类型未知)。
注意:首先GC不认为uintptr是一个活引用,因此uintptr指向的对象可能被gc回收。其次,如果uintptr关联的对象移动,则其值也不会更新,即uintptr无法保持对变量的引用。
Done
// Done decrements the WaitGroup counter by one. func (wg *WaitGroup) Done() { wg.Add(-1) }
Wait
Wait
// Wait blocks until the WaitGroup counter is zero. func (wg *WaitGroup) Wait() { // 获取state1和state2的地址 statep, semap := wg.state() if race.Enabled { _ = *statep // trigger nil deref early race.Disable() } for { // 原语:原子操作获取state1的值 state := atomic.LoadUint64(statep) v := int32(state >> 32) // counter,活跃的g的数量 w := uint32(state) // waiters // 如果没有活跃的g了,直接返回 if v == 0 { if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(wg)) } return } // CAS操作增加waiters的数量 if atomic.CompareAndSwapUint64(statep, state, state+1) { // 竞争检测代码,不看 if race.Enabled && w == 0 { // Wait must be synchronized with the first Add. // Need to model this is as a write to race with the read in Add. // As a consequence, can do the write only for the first waiter, // otherwise concurrent Waits will race with each other. race.Write(unsafe.Pointer(semap)) } // 到这一步,说明当前g要阻塞等待了 // 原语:根据semap的值也就是state2的地址找到相应的阻塞队列,把当前g放进去,并挂起 runtime_Semacquire(semap) if *statep != 0 { panic("sync: WaitGroup is reused before previous Wait has returned") } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(wg)) } return } } }
这篇关于Golang sync.WaitGroup 源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-24MongoDB资料:新手入门完全指南
- 2024-12-20go-zero 框架的 RPC 服务 启动start和停止 底层是怎么实现的?-icode9专业技术文章分享
- 2024-12-19Go-Zero 框架的 RPC 服务启动和停止的基本机制和过程是怎么实现的?-icode9专业技术文章分享
- 2024-12-18怎么在golang中使用gRPC测试mock数据?-icode9专业技术文章分享
- 2024-12-15掌握PageRank算法核心!你离Google优化高手只差一步!
- 2024-12-15GORM 中的标签 gorm:"index"是什么?-icode9专业技术文章分享
- 2024-12-11怎么在 Go 语言中获取 Open vSwitch (OVS) 的桥接信息(Bridge)?-icode9专业技术文章分享
- 2024-12-11怎么用Go 语言的库来与 Open vSwitch 进行交互?-icode9专业技术文章分享
- 2024-12-11怎么在 go-zero 项目中发送阿里云短信?-icode9专业技术文章分享
- 2024-12-11怎么使用阿里云 Go SDK (alibaba-cloud-sdk-go) 发送短信?-icode9专业技术文章分享