开始:

        • 主要结构
        • 主体流程
          • PUT方法
          • GET方法
        • 基准测试

主要结构

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一次使用时,不能复制
type Pool struct {
    noCopy noCopy //空结构,用来防止pool在第一次使用时被复制。

    local     unsafe.Pointer // 本地固定大小的pool池,其实类型为[P]poolLocal
    localSize uintptr        // 本地固定pool池的大小

    victim     unsafe.Pointer // 表示上一个周期的local
    victimSize uintptr        // 同上

    // New 在 pool 中没有获取到,调用该方法生成一个变量
    New func() interface{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//具体存储结构
type poolLocalInternal struct {
    private interface{} // 私有的,只能由自己的 P 使用
    shared  poolChain   // 共享的,可以被任何的 P 使用
}
type poolLocal struct {
    poolLocalInternal

    // Prevents false sharing on widespread platforms with
    // 128 mod (cache line size) = 0 .
    // 避免缓存 false sharing,使不同的线程操纵不同的缓存行,多核的情况下提升效率。
    pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

主体流程

看完整个结构后,我们先了解一下整个流程。

PUT方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (p *Pool) Put(x interface{}) {
    ...
    //获取当前p的pool(池)
    l, _ := p.pin()
    if l.private == nil {
    // 如果私有属性为nil,那么将x写入到private
        l.private = x
        x = nil
    }
    if x != nil {
    //如果x没有复制给私有属性,则将其推到共享属性中
        l.shared.pushHead(x)
    }
    runtime_procUnpin()
    if race.Enabled {
        race.Enable()
    }
}
GET方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (p *Pool) Get() interface{} {
    if race.Enabled {
        race.Disable()
    }
    // 获取当前p的pool(池)
    l, pid := p.pin()
    x := l.private
    l.private = nil
    if x == nil {
        // 从当前 P 的 shared 末尾取一个
        x, _ = l.shared.popHead()
        if x == nil {
        // 还没有取到 则去其他 P 的 shared 取
            x = p.getSlow(pid)
        }
    }
    runtime_procUnpin()
    if race.Enabled {
        race.Enable()
        if x != nil {
            race.Acquire(poolRaceAddr(x))
        }
    }
    // 最后还没取到 调用 NEW 方法生成一个
    if x == nil && p.New != nil {
        x = p.New()
    }
    return x
}

基准测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
    "sync"
    "testing"
)

type S struct {
    num int
}

func BenchmarkWithPool(b *testing.B) {
    var s *S
    var pool = sync.Pool{
        New: func() interface{} { return new(S) },
    }
    for i := 0; i < b.N; i++ {
        for j := 0; j < 10000; j++ {
            s = pool.Get().(*S)
            s.num = 1
            s.num++
            pool.Put(s)
        }
    }
}

func BenchmarkWithNoPool(b *testing.B) {
    var s *S
    for i := 0; i < b.N; i++ {
        for j := 0; j < 10000; j++ {
            s = &S{num: 1}
            s.num++
        }
    }
}
1
2
3
4
5
6
$ go test -bench=. -benchmem
goos: darwin
goarch: amd64
                                     
BenchmarkWithPool-4                10000            253269 ns/op               0 B/op          0 allocs/op
BenchmarkWithNoPool-4              10000            175742 ns/op           80000 B/op      10000 allocs/op

可以看到每次分配的内存 0 B vs 80000 B,每次内存分配次数 0 vs 10000。因为每次测试,我们执行了10000次迭代,所以看到没使用池的内存单次分配是 8B(即 结构 S 占的内存),单次分配次数为 1次。但是在每次执行的时间上使用池比不使用池是要多的,比较使用池涉及到池的维护,也算是正常的。这样看来,在高并发的场景下,context 的复用率非常高,所带来的 GC 压力也更小,所以效率当然就高了。