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

  • 相关阅读:
    Opengauss到Oracle增量同步, 使用debezium
    1796. 字符串中第二大的数字
    ZooKeeper学习笔记
    电脑mp4格式视频打不开怎么解决?
    第一个mybatis程序,实现增删改查CRUD
    LeetCode --- 1356. Sort Integers by The Number of 1 Bits 解题报告
    Vue学习笔记
    基于ffmpeg开发的多音频文件音量均衡程序
    Windows 系统 Solr 8.11.3 安装详细教程(最新)
    工业数字化转型 — 工业现场总线
  • 原文地址:https://www.cnblogs.com/pengb/p/18001009