0%

《Golang》sync.Pool 源码浅析

在数据库连接中,如果频繁的建立连接、操作、断开连接,那么数据库是吃不消的,需要连接池来复用连接

同样,在程序中,如果对象频繁的创建、回收、创建,这对性能会产生很大的消耗

所以出现了 sync.Pool,它是用来复用对象的,避免对象频繁创建和回收,减轻 GC 压力

sync.Pool 的内容比较少,下面通过分析 Get 和 Put 两个函数来看看 Pool 的实现细节

Put

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
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l, _ := p.pin() // 阻止 m 被抢占,并拿出当前 P 对应的池子 (这里如果不阻止抢占的话,如果当前 g 换了一个 P,就可能会出现同一个 g 操作多个 P 对应的池子,前一半代码操作 A 池子后一半代码操作 B 池子,导致问题)
if l.private == nil { // private 没被塞对象的话就往里塞一个,已经塞了的话就塞到 poolChain 里去
l.private = x
x = nil
}
if x != nil {
l.shared.pushHead(x) // 放到当前 P 对应的 poolChain 链表里去
}
runtime_procUnpin() // 解除抢占
if race.Enabled {
race.Enable()
}
}

sync.Pool 池子中实际上具有 n 个本地池,本地池就是为每个 P 分配的池

是为了防止多线程抢占带来的性能损耗,类比 Golang 内存管理中的 mcache

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
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
l, pid := p.pin() // 阻止抢占,并取出当前 P 对应的本地池
x := l.private // 从 private 字段取出对象
l.private = nil
if x == nil { // 如果 private 字段中没有取到,就去 poolChain 链表里去取
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil { // 如果当前 P 的池子中没有找到,则从其他 P 对应的池子偷
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil { // 如果当前 P 对应的池子中没有取到对象,则 New 一个
x = p.New()
}
return x
}

总结

  1. sync.Pool 池子中实际上具有 MAXPROC 个本地池,每个 P 对应一个本地池,是为了防止多线程抢占带来的性能损耗



微信关注我,及时接收最新技术文章