在源码中学习一些技巧
在go语言的gin框架中,通过.Run()
启动web服务。我们查看源码:
//gin.go
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
其中ListenAndServe
是net/http
库中指定的监听地址和处理器
//net/http/server.go
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
可以看到gin源码中调用时的是engine *Engine
作为Handler
参数,继续查看一下Handler
源码:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
果然没错Handler
是一个接口,接口方法:ServeHTTP(ResponseWriter, *Request)
那么Engine
必然实现了 **ServeHTTP
**方法
通过VSCode
导航,的确找到了Engine
结构体实现了这个方法:
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
我们看上一节的第三行代码:
c := engine.pool.Get().(*Context)
type Engine struct {
//omit code
pool sync.Pool
}
其中pool字段是sync.Pool
类型,那到底什么是sync.Pool
?
原来:这是go语言中典型的对象池的概念,为了减少GC,减少内存申请的频率,把可以重用的对象构造成一个对象池,engine.pool.Get()
就是从池子中捞出一个对象,强制转换为context
指针。
sync.Pool 的使用方式非常简单:
//gin.go
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
//实现New函数
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
New
函数创建。获取
Get()
用于从对象池中获取对象,因为返回值是 interface{}
,因此需要类型转换,上面的代码已经有展示。c := engine.pool.Get().(*Context)
放回
Put()
则是在对象使用完毕后,返回对象池。处理完http请求后,又把context
放回到对象池中:
engine.handleHTTPRequest(c)
engine.pool.Put(c)
sync.Pool
是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。它的设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩。
由于上面加粗字体的原因,所以对象池比较适合用来存储一些临时切状态无关的数据,因为存入对象池的值有可能会在垃圾回收时被删除掉。http请求的context上下文就是这样的类型。
在gin
源码中定义Engine
结构体的下面有一句:
var _ IRouter = &Engine{}
一个不起眼的匿名变量:匿名变量不占用内存空间,不会分配内存,那它到底有什么用?不可能是作者写忘了,这么著名的开源库。
原来go语言经常会遇到一个场景,在编写完一个结构体之后,这个结构体会实现很多接口,问题是,代码繁杂以后,谁还记得哪个接口到底实现没有,怎么办?
Ctrl+F
比对,找方法接收者,嗯,也是一个方法但是,更棒的方法,是靠编译器:
定义一个匿名变量 var _ 接口类型=结构体类型指针
,这个主要是确保结构体确实实现了接口,目的就是把问题暴露在编译阶段,很多第三方库和标准库都是这样做的,值得学习。
https://www.cnblogs.com/sunsky303/p/9706210.html
https://geektutu.com/post/hpg-sync-pool.html
------------------- End -------------------