Mygin上下文之sync.Pool复用

本篇是mygin的第八篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。

目的

  • sync.Pool 的作用介绍
  • mygin中使用sync.Pool

sync.Pool 的作用

先看看官方文档怎样说的吧,我截取了官方文档的第一句。

// A Pool is a set of temporary objects that may be individually saved and retrieved.
.....
  • 简单翻译一下的意思是:池是一组可以单独保存和检索的临时对象。既然可以单独保存和检索的临时对象,对于大量重复地创建许多对象,造成 GC 的工作量巨大。而mygin的模式是 责任链模式 ,因此满足使用 sync.Pool。
  • 一个 Pool 可以安全地由多个 goroutine 同时使用。池的目的是缓存已分配但未使用的项目以供以后重用,从而减轻垃圾回收器的压力。
  • sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来可能会使用的值。这样就可以不用再次经过内存分配,可直接复用已有对象,减轻 GC 的压力,从而提升系统的性能。
    以上都是源于官方文档翻译的,文档中还提到fmt包中,打印也使用了sync.Pool,感兴趣的可以点进源码查看。

sync.Pool 使用

sync.Pool 的使用方式非常简单:
只需要实现New函数即可。对象池中没有对象时,将会调用New函数创建,我使用了mygin中的context

创建

var contextPool = sync.Pool{
	New: func() interface{} {
		return new(Context)
	},
}

使用和归还

c := contextPool.Get().(*Context)
json.Marshal(c)
contextPool.Put(c)

测试

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)
	}
}

测试结果:

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 方法中

修改前

//实例化一个下上文
c := &Context{
	Request:  r,
	Writer:   w,
	Params:   params,
	handlers: handlers,
	index:    -1,
}

修改后

//从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方法代码如下

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测试

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压力会减轻不少。我设计的上下文中内容很少,随着功能的增多,效果会更加明显。

posted @ 2024-02-01 13:18  Scott_pb  阅读(117)  评论(0编辑  收藏  举报