看过啥底层包?

参考解析

题目来源:

答案:

这里就简单说一下sync.WaitGroup

WaitGroup包含三个方法:

  1. wg.Add(int)
  2. wg.Done()
  3. wg.Wait()

Add可以设置WaitGroup的计数值,一般放在前面写
Done用来将计数值-1,写在前置goroutine里
Wait会阻塞当前的goroutine,直到WaitGroup的计数值为0,写在需要等待的goroutine里,很多情况下是main goroutine。

使用WaitGroup后,可以确保主协程会在前面的goroutine结束之后才会继续。

nocopy字段

对于一些不应该被复制的结构体,可以在里面增加nocopy字段,它的底层是:

  1. type noCopy struct{}
  2. // Lock is a no-op used by -copylocks checker from `go vet`.
  3. func (*noCopy) Lock() {}
  4. func (*noCopy) UnLock() {}

通过go vet可以检测是否有不应该的复制:

Copy passes lock by value: main.Demo contains main.noCopy

源码解析

  1. type WaitGroup struct {
  2. // noCopy字段,防止WaitGroup实例被copy
  3. noCopy noCopy
  4. // 前两个元素作为state,后一个元素作为信号量
  5. // 第一个是当前阻塞的goroutine个数,第二个是WaitGroup计数值,第三个是信号量
  6. // 对于32bit和64bit系统,字段有些许差别,后面有代码表示,这里不作讨论
  7. state1 [3]uint32
  8. }
  1. func (wg *WaitGroup) Add(delta int) {
  2. // 获取state和信号量
  3. statep, semap := wg.state()
  4. // 高32bit是计数值,所以把delta左移32位加到WaitGroup计数值上
  5. state := atomic.AddUint64(statep, uint64(delta)<<32)
  6. // 当前计数值
  7. v := int32(state >> 32)
  8. // 等待者数量
  9. w := uint32(state)
  10. // 计数值大于0或者w等于0,直接正常的返回
  11. if v > 0 || w == 0 {
  12. return
  13. }
  14. // 接下来的情况就是计数值等于0,但是还有等待者的情况,此时等待的已经没有意义
  15. // 比如说一开始只设了wg.Add(3),结果启动了五个goroutine里面都有Done,Done了三次之后,剩下的两个协程不会等它们执行完了。
  16. // 直接把组合的statep设为0(v和w都设为0)
  17. *statep = 0
  18. for ; w != 0; w-- {
  19. runtime_Semrelease(semap, false, 0)
  20. }
  21. }

func (wg *WaitGroup) Done() { // 非常简单,就是调Add(-1)即可 wg.Add(-1)}

  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 } }}