发布于:,更新于:
07.Mygin中sync.Pool应用
本篇是mygin的第六篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。
目的
- sync.Pool 的作用介绍
- mygin中使用sync.Pool
sync.Pool 的作用
先看看官方文档怎样说的吧,我截取了官方文档的第一句。
- 简单翻译一下的意思是:池是一组可以单独保存和检索的临时对象。既然可以单独保存和检索的临时对象,对于大量重复地创建许多对象,造成 GC 的工作量巨大。而mygin的模式是 责任链模式 ,因此满足使用 sync.Pool。
- 一个 Pool 可以安全地由多个 goroutine 同时使用。池的目的是缓存已分配但未使用的项目以供以后重用,从而减轻垃圾回收器的压力。
- sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来可能会使用的值。这样就可以不用再次经过内存分配,可直接复用已有对象,减轻 GC 的压力,从而提升系统的性能。
以上都是源于官方文档翻译的,文档中还提到fmt包中,打印也使用了sync.Pool,感兴趣的可以点进源码查看。
sync.Pool 使用
sync.Pool 的使用方式非常简单:
只需要实现New函数即可。对象池中没有对象时,将会调用New函数创建,我使用了mygin中的context
创建
1 2 3 4 5
| var contextPool = sync.Pool{ New: func() interface{} { return new(Context) }, }
|
使用和归还
1 2 3
| c := contextPool.Get().(*Context) json.Marshal(c) contextPool.Put(c)
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func BenchmarkUnmarshal(b *testing.B) { for n := 0; n < b.N; n++ { c := &Context{}
json.Marshal(c) } }
func BenchmarkUnmarshalWithPool(b *testing.B) { for n := 0; n < b.N; n++ { c := contextPool.Get().(*Context) json.Marshal(c) contextPool.Put(c) } }
|
测试结果:
1 2 3 4 5 6 7 8 9
| go test -bench . -benchmem goos: linux goarch: amd64 pkg: github.com/scott-pb/mygin cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz BenchmarkUnmarshal-8 5888780 208.1 ns/op 144 B/op 2 allocs/op BenchmarkUnmarshalWithPool-8 7261801 165.0 ns/op 48 B/op 1 allocs/op PASS ok github.com/scott-pb/mygin 2.808s
|
在这个例子中,可以看出,使用了 sync.Pool 后,内存占用仅为未使用的 48/144= 1/3,对 GC 的影响就很大了。执行速度也快了,当然不同的设备测试结果也会不同。
测试源码
我把测试源码放在了mygin中 mygin/context_test.go
mygin使用sync.Pool
修改mygin/engine.go
修改engine.go中实例化conetxt的部分具体在ServeHTTP 方法中
修改前
1 2 3 4 5 6 7 8
| c := &Context{ Request: r, Writer: w, Params: params, handlers: handlers, index: -1, }
|
修改后
1 2 3 4 5 6 7 8 9 10 11 12 13
| //从pool中取 c := e.pool.Get().(*Context) c.Request = r c.Writer = w c.Params = params c.handlers = handlers c.index = -1
// 执行处理函数链 c.Next()
//归还到pool中 e.pool.Put(c)
|
mygin测试
main方法代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "github.com/scott-pb/mygin" "net/http" )
func main() {
r := mygin.Default() group := r.Group("/api") group.GET("/test", func(c *mygin.Context) { c.String(http.StatusOK, "success!\n") })
err := r.Run(":8088") if err != nil { fmt.Println(err) } }
|
curl测试
1 2 3 4 5 6 7
| curl -i http://localhost:8088/api/test HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Date: Thu, 01 Feb 2024 05:08:52 GMT Content-Length: 9
success!
|
这样mygin的context上下文就加入了Pool池,对于高并发情况下的GC压力会减轻不少。我设计的上下文中内容很少,随着功能的增多,效果会更加明显。