看过啥底层包?
参考解析
题目来源:
答案:
这里就简单说一下sync.WaitGroup
WaitGroup包含三个方法:
wg.Add(int)
wg.Done()
wg.Wait()
Add可以设置WaitGroup的计数值,一般放在前面写
Done用来将计数值-1,写在前置goroutine里
Wait会阻塞当前的goroutine,直到WaitGroup的计数值为0,写在需要等待的goroutine里,很多情况下是main goroutine。
使用WaitGroup后,可以确保主协程会在前面的goroutine结束之后才会继续。
nocopy字段
对于一些不应该被复制的结构体,可以在里面增加nocopy
字段,它的底层是:
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) UnLock() {}
通过go vet
可以检测是否有不应该的复制:
Copy passes lock by value: main.Demo contains main.noCopy
源码解析
type WaitGroup struct {
// noCopy字段,防止WaitGroup实例被copy
noCopy noCopy
// 前两个元素作为state,后一个元素作为信号量
// 第一个是当前阻塞的goroutine个数,第二个是WaitGroup计数值,第三个是信号量
// 对于32bit和64bit系统,字段有些许差别,后面有代码表示,这里不作讨论
state1 [3]uint32
}
func (wg *WaitGroup) Add(delta int) {
// 获取state和信号量
statep, semap := wg.state()
// 高32bit是计数值,所以把delta左移32位加到WaitGroup计数值上
state := atomic.AddUint64(statep, uint64(delta)<<32)
// 当前计数值
v := int32(state >> 32)
// 等待者数量
w := uint32(state)
// 计数值大于0或者w等于0,直接正常的返回
if v > 0 || w == 0 {
return
}
// 接下来的情况就是计数值等于0,但是还有等待者的情况,此时等待的已经没有意义
// 比如说一开始只设了wg.Add(3),结果启动了五个goroutine里面都有Done,Done了三次之后,剩下的两个协程不会等它们执行完了。
// 直接把组合的statep设为0(v和w都设为0)
*statep = 0
for ; w != 0; w-- {
runtime_Semrelease(semap, false, 0)
}
}
func (wg *WaitGroup) Done() { // 非常简单,就是调Add(-1)即可 wg.Add(-1)}
// 一直阻塞,直到state的计数值=0func (wg *WaitGroup) Wait() { statep, semap := wg.state() for { state := atomic.LoadUint64(statep) v := int32(state >> 32) w := uint32(state) // 如果为0.直接返回 if v == 0 { return } // 否则阻塞 if atomic.CompareAndSwapUint64(statep, state, state+1) { runtime_Semacquire(semap) return } }}