MPG模式
1、要了解sync.Pool得先 了解golang的调度模型MPG。
2、MPG就是让 Go程序 智能地 将 goroutine协程中的 任务合理的分配给每个CPU。goroutine协程 是由Go 的运行时(runtime)调度和管理的,所以 MPG 就是 Go语言运行时(runtime)层面的实现。
3、MPG包含三个部分:M(内核线程)、P(管理一组go协程)、G(一个go协程)
M:Machine,一个M直接关联了一个内核线程, 一个groutine最终是要放到M上执行的;
P:processor,存储当前go协程运行的上下文环境(函数指针,堆栈地址及地址边界),会对自己的go协程队列做一些调度(把占用CPU时间较长的go协程暂停、运行后续的goroutine)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务;
G:指的是Goroutine,其实本质上也是一种轻量级的线程,里面除了存放本goroutine信息外 还有与所在P的绑定等信息
参考文档:https://studygolang.com/articles/26904
sync.Pool
1、golang在 高并发场景下应用时,由于内建的GC垃圾回收机制会影响应用的性能,为了减少GC垃圾回收,golang提供了对象重用的机制,也就是sync.Pool对象池
2、sync.Pool 的Pool是一个临时对象池,对Pool的操作就是 对当前 goroutine绑定的P(MPG的P)进行操作
3、 Pool对象 可以被看作是一个存放可重用对象的值的容器。 (该对象是临时的,暂时不用的)
4、其特点:sync.Pool是可伸缩的,并发安全的。 任何Pool中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩
举例应用
func main() {
var coll = &sync.Pool{
New: func() interface{
}{
return "hello, shenzhen"
}}
a := "xiasha"
coll.Put(a)
b := coll.Get()
fmt.Println("第一次取出的东西是:",b)
c := coll.Get()
fmt.Println("第二次取出的东西是:",c)
}
输出:
第一次取出的东西是: xiasha
第二次取出的东西是: hello, shenzhen
底层实现
- Pool结构体对象
该对象内,除了New方法可以供外部调用,在对象池内生成一个默认对象,其他都是私有成员
[p]是指MPG模型里的p
type Pool struct {
noCopy noCopy // 实现了Locker接口的空结构体
local unsafe.Pointer // 本地固定大小per-P池,实际类型为[P]poolLocal ,有多少个P数组就有多大,也就是每个P维护了一个本地的poolLocal。
localSize uintptr // 本地数组的大小
victim unsafe.Pointer // 上一周期的局部
victimSize uintptr // 已使用的数组大小
// New 方法在 Get 失败的情况下,选择性的创建一个值,Get就会返回该值, 否则返回nil
New func() interface{
}
}
Pool结构体对象的local 字段,因为本质上是一个poollocal类型,所以介绍一下该类型
如下:每个P(对应CPU)都分配一个本地池,当执行Get或者Put操作的时候,会先将goroutine和某个P的子池关联,再对该子池进行操作
poolLocal 包装了poolLocalInternal 结构体对象,新增了pad成员
type poolLocalInternal struct {
private interface{
} // 私有对象,只能被特定的P访问;因为同一时刻一个P只能执行一个goroutine,所以无需加锁
shared poolChain // 共享列表对象,可以被任何P访问;对共享列表对象进行操作时,因为可能有多个goroutine同时操作,所以需要加锁。
// 自己可以从队列的头部存然后从头部取,而别的P可以从尾部取。
}
type poolLocal struct {
poolLocalInternal
// 防止在128 mod(缓存线大小)=0的广泛平台上进行false sharing错误共享。
// cache使用中常见的一个问题是false sharing。当不同的线程同时读写同一cache line上不同数据时就可能发生false sharing
// false sharing会导致多核处理器上严重的系统性能下降
pad [128 - unsafe.Sizeof(poolLocalInternal{
})%128]byte
}
- Put方法实现
新建一个Pool池子实例后,需要 调用Put方法,Put(x)就是把一个临时对象x放入 该池子里,源码如下:
func