Go圣经-学习笔记之defer和异常处理

上一篇 Go圣经-学习笔记之函数值(二)

下一篇 Go圣经-学习笔记之方法

可变参数

形参数量可变的函数称为可变参数函数。使用最多的可变参数函数标准库:fmt

在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号...

func 函数名(t Type1, t Type2, tn ...TypeN) 返回参数列表 {}

// DEMO 
func sum(x, y ...int) int {
    count:=x
    for _, elem:= range y {
        count+=elem
    }
    return count
}

fmt.Println(sum(1,2,3,4,5,6,7,8)) // 使用方法1

elems:=[]int{2,3,4,5,6,7,8}
fmt.Println(sum(1, elems...) // 使用方法2

defer函数

defer表示函数退出时,栈内的函数列表退栈执行。这个主要介绍defer和return之间的关系

func Incr() (i int) {
    defer func(){
       i++ 
    }()
    return 1
}

fmt.Println(Incr()) // 打印值i = 1 ?

实际上defer和return的执行顺序是没有什么问题的。先执行defer,然后再return。重点是 return xx。前面章节说过,它不是一个原子操作。分解动作为:

func Incr() (i int) {
    defer func() {
        i++
    }()
    i=1
    return
}

第二个是我在写Go服务端时的一个小经验,首先说一下背景,我们一般想做日志追踪或者请求链的调用追踪,看看程序执行到哪个步骤然后出错了。这时,我们一般会这样做:

func AddSaleOrder(so *SaleOrder, o *orm.Ormer) (retCode int, err error) {
    ......
    Logger.Info("[%d] enter AddSaleOrder.", so.SaleOrderId)
    defer Logger.Info("[%d] left AddSaleOrder.", so.SaleOrderId)
    ......
    return
}

但是为了懒不想写两句,或者节约代码长度什么的。然后就采用了写出了下面这种方式:

func bigSlowOp() {
    defer TraceLog("bigSlowOp")()
    fmt.Println("hello,world")
    time.Sleep(3 * time.Second)
    return
}

func TraceLog(methodName string, params ...interface{}) func() {
    start := time.Now()
    fmt.Printf("[%v] enter %s\n", params, methodName)
    return func() {
        fmt.Printf("[time: %s |  %v] left %s\n", time.Since(start), params, methodName)
    }
}

func main() {
    bigSlowOp()
    return
}
// 输出结果
/*
[[]] enter bigSlowOp
hello,world
[time: 3.000241212s |  []] left bigSlowOp
*/

那么,我们以后只需要写defer TraceLog(方法名,参数列表)()就OK了

defer语句经常用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁等,无论函数是正常执行完成,还是提前退出或者panic,defer堆栈里的函数列表都会执行。那么资源也可以释放。

同时,我们也要注意引入defer,如果不注意,可能会带来资源泄露问题:

for _, filename:= range filenames{
    f, err := os.Open(filename)
    if err !=nil{
        return
    }
    defer f.Close() // 因为f的生命周期一直在for循环内,所以defer在退出之前不会执行。如果文件列表过大,就是造成文件描述符大量泄露,如果是网络,则会出现"connection peer by peer"的相关错误。
}

// 解决方案:使f变量的生命周期缩短,和for循环的执行周期分离开
func doFile(filename string) error {
    f, err := os.Open(filename)
    if err !=nil{
        return err
    }
    defer f.Close()
    return nil
}

for _, filename:= range filenames {
    err := doFile(filename)
}

一旦有生命周期的障碍,首先想到函数体执行的生命周期。

Panic异常

一般而言程序在运行过程中,发生了数组越界,空指针引用错误等,这些错误会引起panic异常。当发生panic时,程序会中断运行,并立即在Goroutine中被延迟的函数。随后并输出日志信息,包括发生错误值,以及上下文调用栈。

但是并不是所有的panic都来自运行时,开发者也可以直接调用panic函数引发panic异常;panic接收任何参数。为了方便诊断问题,标准库runtime允许开发者输出堆栈信息。例如:

func main(){
    defer printStack()
    f(3)
}

func printStack() {
    var buf [4096]byte
    runtime.Stack(buf[:], false)
    os.Stdout.Write(buf[:n])
}

Recover捕获异常

通常来说,不应该对panic异常做任何处理。让异常尽量早暴露早修复,提高软件的可用性。但有时也需要从异常恢复,至少让程序崩溃前做一些操作,比如: 如果网络服务端挂掉,如果服务端不做任何处理,直接panic,则客户端迟迟得不到服务端的响应,影响用户体验。

如果在defer内调用了内置函数recover,并且定义该defer的函数发生了panic,recover会使程序从panic恢复,并返回panic value。导致panic异常的函数不会再运行,但正常返回。在未发生panic时,调用recover会返回nil。

这里要说明一下,至于panic被哪里的recover捕获,或者不会捕获,可以参考这篇文章: panic和recover工作原理

后续我会通过雨痕老师的Go源码分析书籍,深入分析panic和recover,这样更容易深入地理解。

转载于:https://my.oschina.net/u/3287304/blog/1556245

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值