首页 > 其他分享 >Go语言精进之路读书笔记第22条——使用defer让函数更简介、更健壮

Go语言精进之路读书笔记第22条——使用defer让函数更简介、更健壮

时间:2024-02-13 11:22:23浏览次数:29  
标签:defer 函数 22 读书笔记 int deferred func sl

22.1 defer的运行机制

  • 在Go中,只有在函数和方法内部才能使用defer。
  • defer关键字后面只能接函数或方法,这些函数被成为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数的栈数据结构中。
  • 在执行defer的函数退出前,按后进先出(LIFO)的顺序调度执行。

22.2 defer的常见用法

最基本、最常见:用于释放资源

1.拦截panic

按需要对panic进行处理,可以尝试从panic中回复(也是Go语言中唯一的从panic中恢复的手段),也可以触发一个新的panic,但为panic传一个新的error值

// $GOROOT/src/bytes/buffer.go
func makeSlice(n int) []byte {
    // If the make fails, give a known error.
    defer func() {
        if recover() != nil {
            panic(ErrTooLarge)
        }
    }()
    return make([]byte, n)
}

deferred函数虽然可以拦截绝大部分的panic,但无法拦截并恢复一些运行时之外的致命问题,比如通过C代码“制造”的崩溃。

2.修改函数的具名返回值

// $GOROOT/src/fmt/scan.go
func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
    defer func() {
        if e := recover(); e != nil {
            if se, ok := e.(scanError); ok {
                err = se.err
            } else {
                panic(e)
            }
        }
    }()
    ...
}
// $GOROOT/src/net/ipsock_plan9.go
func dialPlan9(ctx context.Context, net string, laddr, raddr Addr) (fd *netFD, err error) {
    defer func() { fixErr(err) }()
    ...
}

// 再来一个更直观的例子
func foo(a, b int) (x, y int) {
    defer func() {
        x = x * 5
        y = y * 10
    }()

    x = a + 5
    y = b + 6
    return
}

3.输出调试信息

// $GOROOT/src/net/conf.go
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
    if c.dnsDebugLevel > 1 {
        defer func() {
            print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
        }()
    }
    ...
}

在出入函数时打印留痕日志(一般在debug日志级别下)

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}
func un(s string) {
    fmt.Println("leaving:", s)
}
func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}
func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}
func main() {
    b()
}

4.还原变量旧值

// $GOROOT/src/syscall/fs_nacl.go
func init() {
    oldFsinit := fsinit
    defer func() { fsinit = oldFsinit }()
    fsinit = func() {}
    ...
}

先将fsinit存储在局部变量oldFsinit中,然后在deferred函数将fsinit的值重新置为存储在oldFsinit中的旧值。

22.3 关于defer的几个关键问题

1.明确哪些函数可以作为deferred函数

内置函数append/cap/complex/imag/len/make/new/real不可以直接作为deferred函数

func bar() (int, int) {
    return 1, 2
}

func fooD() {
    // builtin functions:
    //    append cap close complex copy delete imag len
    //     make new panic print println real recover

    var c chan int
    var sl []int
    var m = make(map[string]int, 10)
    m["item1"] = 1
    m["item2"] = 2
    var a = complex(1.0, -1.4)

    var sl1 []int

    defer bar()
    //defer append(sl, 11)
    //defer cap(sl)
    defer close(c)
    //defer complex(2, -2)
    defer copy(sl1, sl)
    defer delete(m, "item2")
    //defer imag(a)
    //defer len(sl)
    //defer make([]int, 10)
    //defer new(*int)
    defer panic(1)
    defer print("hello, defer\n")
    defer println("hello, defer")
    //defer real(a)
    defer recover()
}
  • 对于有返回值的自定义函数或方法,返回值会在deferred函数被调用执行的时候被自动丢弃
  • 对于哪些不能直接作为deferred函数的内置函数,可以使用一个包裹不能直接作为deferred函数的匿名函数来间接满足要求(意义不大)
defer func() {
    _ = append(s1, 11)
}

2.把握好defer关键字后表达式的求值时机

defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。

例子1

  • 函数&带参数的匿名函数,传入for循环每次i的值
  • 不带参数的匿名函数,传入for循环结束后i=4
func foo1() {
    for i := 0; i <= 3; i++ {
        defer fmt.Println(i)
    }
}

func foo2() {
    for i := 0; i <= 3; i++ {
        defer func(n int) {
            fmt.Println(n)
        }(i)
    }
}

func foo3() {
    for i := 0; i <= 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}

func main() {
    fmt.Println("foo1 result:")
    foo1() //3 2 1 0
    fmt.Println("\nfoo2 result:")
    foo2() //3 2 1 0
    fmt.Println("\nfoo3 result:")
    foo3() //4 4 4 4
}

例子2

  • foo11:传入的变量s1的值为[]int{1,2,3},因此压入deferred函数栈的函数是func([]int{1,2,3})
  • foo22:传入的变量为s1的地址,因此压入deferred函数栈的函数是func(&s1)
func foo11() {
    sl := []int{1, 2, 3}
    defer func(a []int) {
        fmt.Println(a)
    }(sl)

    sl = []int{3, 2, 1}
    _ = sl
}
func foo22() {
    sl := []int{1, 2, 3}
    defer func(p *[]int) {
        fmt.Println(*p)
    }(&sl)

    sl = []int{3, 2, 1}
    _ = sl
}

func main() {
    foo11() // [1 2 3]
    foo22() // [3 2 1]
}

3.知晓defer带来的性能损耗

Go1.14版本中,defer性能提升巨大,已经和不使用defer的性能相差很小了

标签:defer,函数,22,读书笔记,int,deferred,func,sl
From: https://www.cnblogs.com/brynchen/p/18014424

相关文章

  • Go语言精进之路读书笔记第21条——让自己习惯于函数是"一等公民"
    21.1什么是"一等公民"(1)正常创建//$GOROOT/src/fmt/print.gofuncnewPrinter()*pp{p:=ppFree.Get().(*pp)p.panicking=falsep.erroring=falsep.wrapErrs=falsep.fmt.init(&p.buf)returnp}(2)在函数内创建,定义匿名函数赋值给......
  • 力扣 递归 迭代 栈 广度 队列 之 226. 翻转二叉树
    给你一棵二叉树的根节点root,翻转这棵二叉树,并返回其根节点。 示例1:输入:root=[4,2,7,1,3,6,9]输出:[4,7,2,9,6,3,1]示例2:输入:root=[2,1,3]输出:[2,3,1]示例3:输入:root=[]输出:[]栈/** *Definitionforabinarytreenode. *publicclassTreeNode......
  • Go语言精进之路读书笔记第20条——在init函数中检查包级变量的初始状态
    20.1认识init函数init函数的特点:运行时调用,Go程序中不能显式调用顺序执行,等待一个init函数执行完毕并返回后再执行下一个init函数在整个Go程序生命周期内仅会被执行一次先被传递给Go编译器的源文件中的init函数先被执行,同一个源文件中的多个init函数按声明顺序依次执行。但......
  • defer slice 和 map 面向对象的特征
    5、deferdefer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。defer作用:●释放占用的资源●捕捉处理异常●输出日志结果如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。  recover错误拦截运行时panic异常......
  • P9725 [EC Final 2022] Chase Game 2
    原题链接题解1.添加几条边,使得对于任意节点,都有环存在,且所在最小环的大小皆大于32.看成有中心点的散发图,最优添加情况为叶子节点相连3.如果两个叶子节点的父节点为lca,那么这两个叶子节点不能直接相连4.看成若干个菊花,每朵菊花的花瓣都是叶子节点,同一朵花上的花瓣不能直接相连......
  • 一月读书笔记《程序员修炼之道:从小工到专家》
    首先,书中对个人责任和职业发展观念的深刻阐述,让我深受触动。我意识到,作为一名程序员,我们所承担的不仅仅是一份工作,更是一份沉甸甸的责任。每一行代码、每一个程序都是我们用心创造的作品,它们不仅仅是为了完成任务而存在,更是在无形中推动着社会的进步。我们的工作成果可能会影响到......
  • 一月读书笔记《人月神话》
    《人月神话》读后感作为一名学生,我对软件工程领域一直充满好奇和热情。在这个信息技术迅猛发展的时代,软件已经渗透到我们生活的方方面面。《人月神话》是软件工程领域的经典之作,由经验丰富的软件项目经理FrederickP.Brooks,Jr.所著。作者以自己在IBM公司担任大型软件项目经理......
  • 一月读书笔记《梦断代码》
    《梦断代码》主要围绕OSAF主持的Chandler项目进行展开,深入剖析了软件开发过程中的种种问题和挑战。通过作者的详细叙述,我仿佛置身于项目的开发现场,亲身经历了那些充满波折和挫折的时刻。首先,书中对软件开发复杂性的描述让我深感震撼。在Chandler项目的开发过程中,作者展示了各种技......
  • 代码随想录算法训练营第十六天| 104.二叉树的最大深度 559.n叉树的最大深度 111.二
    104.二叉树的最大深度  题目链接:104.二叉树的最大深度-力扣(LeetCode)n叉树也一样思路:我的普通递归方法classSolution{public:intdepth(TreeNode*node,intd){intl=0,r=0;if(node->left==NULL&&node->right==NULL)returnd;if(node-......
  • VS2022+OpenCV_contrib安装
    准备:Cmake,OpenCV安装包,OpenCV扩展包安装步骤:一:OpenCV扩展包编译打开文件夹新建一个文件夹 打开cmake开始编译第一栏Whereisthesourcecode是指OpenCV解压后得到的source文件的路径;第二栏wheretobuildthebinaries是指编译后输出文件的路径,直接在opencv的同个大文件......