Mygin上下文之sync.Pool复用

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

目的

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

sync.Pool 的作用

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

Golang 复制代码
// 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

创建

Golang 复制代码
var contextPool = sync.Pool{
	New: func() interface{} {
		return new(Context)
	},
}

使用和归还

Golang 复制代码
c := contextPool.Get().(*Context)
json.Marshal(c)
contextPool.Put(c)

测试

Golang 复制代码
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)
	}
}

测试结果:

shell 复制代码
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 方法中

修改前

Golang 复制代码
//实例化一个下上文
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方法代码如下

Golang 复制代码
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测试

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

相关推荐
澡点睡觉4 小时前
【golang长途旅行第32站】反射
开发语言·后端·golang
007php0077 小时前
使用 Docker、Jenkins、Harbor 和 GitLab 构建 CI/CD 流水线
数据库·ci/cd·docker·容器·golang·gitlab·jenkins
好学且牛逼的马7 小时前
golang6 条件循环
golang
不过普通话一乙不改名15 小时前
第四章:并发编程的基石与高级模式之Select语句与多路复用
开发语言·golang
Pure_Eyes17 小时前
go 常见面试题
开发语言·后端·golang
{⌐■_■}21 小时前
【ElasticSearch】使用docker compose,通过编写yml安装es8.15和kibana可视化界面操作,go连接es
elasticsearch·docker·golang
Tony Bai2 天前
泛型重塑 Go 错误检查:errors.As 的下一站 AsA?
开发语言·后端·golang
ALex_zry2 天前
Golang云端编程深度指南:架构本质与高阶实践
开发语言·架构·golang
ALex_zry2 天前
Go语言中的迭代器模式与安全访问实践
安全·golang·迭代器模式
君万2 天前
【go语言】字符串函数
爬虫·python·golang