sync.Pool 内部实现

sync.Pool 是 Golang 官方提供的缓存工具。最近看了下它的源码,简单写一下内部实现方法。

数据存储结构

数据存储结构为一个 poolLocal 类型的数组,数组名称为 local。数组下标含义为 P 的 id,即每个 P 有一个独立的数据存储空间 poolLocal,可通过 local[pid] 来访问到自己的 poolLocal。poolLocal 包含一个 interface 类型的私有区,和一个 []interface{} 类型的共享区。私有区只有本 P 的 goroutine 可以访问,共享区可以被所有 goroutine 访问。

Put 方法

  1. 传入 interface 类型的参数 x,若 x 为空,则直接返回。若 x 不为空,则继续以下逻辑。
  2. 获取当前 P 的 poolLocal,并禁止当前 P 的 goroutine 抢占调度,从而能安全访问私有区。
  3. 访问当前 poolLocal 的私有区,若私有区为空,则将传入数据赋值给私有区,然后恢复当前 P 抢占调度设置。对私有区的访问不涉及加锁。
  4. 若当前 poolLocal 的私有区已经存有数据了,则对 poolLocal 的共享区加锁,并将数据追加到共享区数组的末尾。

Get 方法

  1. 获取当前 P 的 poolLocal,并禁止当前 P 的 goroutine 抢占调度,从而能安全访问私有区。
  2. 从 poolLocal 的私有区取出数据,并将私有区置空。然后恢复当前 P 抢占调度设置。对私有区的访问不涉及加锁。
  3. 若从 poolLocal 的私有区取出的数据不为空,则返回此数据。
  4. 若从 poolLocal 的私有区取出的数据为空,则对 poolLocal 的共享区加锁,并访问共享区数组数据。
  5. 若共享区数组长度不为 0, 则返回并删除共享区数组尾部数据。
  6. 若共享区数组长度为 0,则从当前 pid (即 P 的 id 号) 的下一个 id 开始,通过 local 数组,依次访问各 P 的 poolLocal 共享区,重复 4、5 操作。若获取到数据则中止遍历并返回数据。
  7. 若 6 步骤未能获取到数据,则调用 New 方法返回新构建数据;若未设置 New 方法,则返回 nil。

获取当前 P poolLocal 方法 (pin pinSlow)

  1. 获取当前 P 的 pid,并禁止当前 P 的 goroutine 抢占调度。
  2. 获取存储所有 P poolLocal 的数组 local,以及 local 的长度 localSize。由于禁止了当前 P 的 goroutine 抢占调度,在获取 local 和 localSize 之间不会发生 GC,从而影响二者的正确性。
  3. 若当前 P 的 pid 小于 localSize,则取 local[pid] 为当前 P 的 poolLocal。
  4. 若当前 P 的 pid 不小于 localSize,则说明 GC 回收了 pool,或 pool 并未初始化过。则创建一个当前并发大小的 local 数组,然后返回 local[pid]。

清除方法 (poolCleanup)

poolCleanup 是在初始化时候注册到 GC 中的,每此 GC 都会调用,从而防止缓存内容过大。

  1. 将 local 中各 poolLocal 的私有区和共享区清空。
  2. 将 local 清空

转载于:https://my.oschina.net/dokia/blog/2221872

你可能感兴趣的:(sync.Pool 内部实现)