别摘掉嫩芽,他们的时代才刚刚开始.....
正如在之前提到的,defer后的函数并不会立即执行,而是推迟到了函数结束后执行。这一特性一般用于资源的释放,例如在加锁之后立即延迟调用l.unlock在函数退出时即完成解锁。
l.lock()
defer l.unlock()
延迟执行的特性除了可以用于前面提到的资源释放和异常捕获,有时也用于函数的中间件。例如,对http服务器进行简单的中间件封装。该中间件的目的是捕获所有的http操作执行异常,并向客户端返回500异常。
func recoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
如下所示的http服务器,可以轻松地加入recoverHandler中间件而不破坏代码原本的结构,这在第三方框架中使用广泛。
func (m MyHandler) ServeHTTP( w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,"hello world")
}
func main(){
handler :=MyHandler{}
server:= http.Server{
Addr:"127.0.0.1:8080",
Handler:recoverHandler(handler),
}
server.ListenAndServe()
}
还可以设计一种类似计算函数执行时间的日志,也可以依靠defer函数实现。
before:= time.Now()
defer func(){
after := time.Now()
fmt.Println(after.After(before))
}()
defer的另一个特性是参数的预计算。这一特性时常导致开发者在使用defer时犯错。因为在大部分时候,我们记住的都是其延迟执行的特性。参数的预计算指当函数到达defer语句时,延迟调用的参数将立即求值,传递到defer函数中的参数将预先被固定,而不会等到函数执行完成后再传递参数到defer中。
如下例所示,defer后的函数需要传递int参数,首先将a赋值为1,接着defer函数的参数传递为a+1,最后,在函数返回前a被赋值为99。那么最后defer函数打印出的b值是多少呢?答案是2。原因是传递到defer的参数是预执行的,因此在执行到defer语句时,执行了a+1 并将其保留了起来,直到函数执行完成后才执行defer函数体内的语句。
func main(){
a := 1
defer func(b int) {
fmt.Println("defer b",b)
}(a+1)
a = 99
}
在函数体内部,可能出现多个defer函数。这些defer函数将按照后入先出(last-in,first-out LIFO)的顺序执行,这与栈的执行顺序是相同的。
func main(){
for i:=1;i<5;i++{
defer fmt.Println("start ",i)
}
}
上例是一种复杂的场景,在循环中调用了defer函数。其最终的打印顺序将与正常函数的执行顺序相反,打印为:
start 4
start 3
start 2
start 1
这也意味着后申请的资源将会先得到释放,如下,加锁的顺序为m→n, 释放锁的顺序为n→m。这与习惯中资源释放的方式是相同的。
m.Lock()
defer m.Lock()
n.Lock()
defer n.Lock()
除了前面提到的参数预计算,defer还有一种非常容易犯错的场景,涉及与返回值参数结合。如下所示,函数f中有返回值r,return g之后在defer函数中将g赋值为200。
var g = 100
func f() (r int) {
defer func() {
g = 200
}()
fmt.Printf("f: g = %d\n", g)
return g
}
func main() {
i := f()
fmt.Printf("main: i = %d, g = %d\n", i, g)
}
最后程序的输出结果如下,函数执行返回值i的值为100,g的值为200,从输出结果可以推测出,在return之后,执行了defer函数。
f: g = 100
main: i = 100, g = 200
对上例中的代码稍作修改,如下所示,程序的输出结构为:main: i = 200, g = 100 。从这个结果中,我们又可以推测出defer执行完成后,执行了return语句。因为其返回值i是在defer函数中赋值的。
var g = 100
func f() (r int) {
r = g
defer func() {
r = 200
}()
r = 0
return r
}
func main() {
i := f()
fmt.Printf("main: i = %d, g = %d\n", i, g)
}
但是,上面两种代码输出结果推测出的结论是截然相反的,第1个例子推测出在return执行后,执行了defer函数,而第2个例子推测出return执行前,执行了defer函数。那么到底是怎么回事呢?原因在于return其实并不是一个原子操作,其包含了下面几步:
将返回值保存在栈上->执行defer函数->函数返回
所以第1个例子中的函数f可以翻译为如下伪代码,最终返回值r为100。
g = 100
r = g
g = 200
return
例2中函数f可以翻译为伪如下代码,最终返回值r为200。
g = 100
r = g
r = 0
r = 200
return
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8