无为的成长之路

Go并发原语之Cond, Once, Pool

Cond

  • 结构
    • l Locker
    • nocopy nocopy
    • notify notifyList
  • 实现
    • Wait()
      • 先添加到通知列表(调用runtime_notifyListAdd)
      • 对locker解锁
      • 调用等待接收通知(调用runtime_notifyListWait)
        • 内部实现就是会把协程gopark起来,使其进入等待状态
      • 收到通知后解除阻塞,继续执行locker加锁操作
    • Signal()
      • 通知列表中的一个goroutine(runtime_notifyListNotifyOne)
        • 内部实现就是最终调用goready唤醒等待列表中的第一个goroutine
    • Broadcast()
      • 通知列表中的所有goroutine
    • 注意点
      • 由于Wait方法会先调用Unlock解锁,所以在调用Wait方法前务必先进行加锁操作
      • Wait方法唤醒后应注意使用场景,可以使用for或if进行判断。如果唤醒后不满足条件,需要再次调用Wait方法等待,即可以使用for循环如for c.Wait() {}进行等待
    • 使用场景
      • 类似于达到某个条件后才能继续执行的场景,如生产消费模式,需要生产一个商品后才能获取并消费该商品

Once

  • 结构
    • done uint32
    • metux sync.Metux
  • 实现
    • 通过 automic.LoadUint32(&done),原子获取done检查是否已经操作,已经做完了就直接返回
      • 这里不可以使用cas进行赋值操作,否则如果同时多个goroutine进入时,虽然只有一个会执行,但是其他的goroutine会立即返回。如果刚好要求是需要等待这个函数必须真正执行过的话,其他立即返回的goroutine并不能保证单例函数已经被执行。
    • 否则,加锁,继续检查变量done是否已更新,如果已更新则直接返回
    • 如果还没有被更新,意味着操作还没执行,调用函数,并保证调用结束后最终会将done的值设置成1

Pool

  • 结构
    • local unsafe.Pointer
      • P数组,GMP的P,[P]poolLocal。这里的描述可能有些简化,实际上local指向的是一个包含多个poolLocal的数组,每个P都有一个对应的poolLocal实例。
  • 实现
    • Get() any
      • 优先找当前p的private属性,有则返回。
      • 无则继续找shared队列,有则返回。
      • 无则继续去其他P中偷一个,看看偷成功不(偷其他的共享队列中的值),成功则返回。
      • 失败则继续找victim缓存,如果找到返回。
      • 如果没找到,只能继续进行调用New()函数创建一个。
        • victim保证了GC后的一个性能平稳,避免由于GC导致local对象被回收出现的短暂需要重新创建大量对象。victim将会在第二次GC后被垃圾回收,然后local又会重新添加到这里,保证了至少一个地方有缓存。
    • Put(any x)
      • 检查当前P是否private有值,如果无值,那就放入本地shared共享队列中。
      • 如果有值,那就需要放入自己的共享队列中。