Go
语言中有个 defer
关键字,经常使用于实现延迟函数来保证关键代码的最终执行,常言道: "未雨绸缪方可有备无患".html
延迟函数就是这么一种机制,不管程序是正常返回仍是异常报错,只要存在延迟函数都能保证这部分关键逻辑最终执行,因此用来作些资源清理等操做再合适不过了.golang
平常开发编程中,有些操做老是成双成对出现的,有开始就有结束,有打开就要关闭,还有一些连续依赖关系等等.编程
通常来讲,咱们须要控制结束语句,在合适的位置和时机控制结束语句,手动保证整个程序善始善终,不遗漏清理收尾操做.c#
最多见的拷贝文件操做大体流程以下:数据结构
srcFile, err := os.Open("fib.txt") if err != nil { t.Error(err) return }
dstFile, err := os.Create("fib.txt.bak") if err != nil { t.Error(err) return }
io.Copy(dstFile, srcFile)
dstFile.Close() srcFile.Close()
srcFile.Close()
值得注意的是: 这种拷贝文件的操做须要特别注意操做顺序并且也不要忘记释放资源,好比先打开再关闭等等!函数
func TestCopyFileWithoutDefer(t *testing.T) { srcFile, err := os.Open("fib.txt") if err != nil { t.Error(err) return } dstFile, err := os.Create("fib.txt.bak") if err != nil { t.Error(err) return } io.Copy(dstFile, srcFile) dstFile.Close() srcFile.Close() }
「雪之梦技术驿站」: 上述代码逻辑仍是清晰简单的,可能不会忘记释放资源也能保证操做顺序,可是若是逻辑代码比较复杂的状况,这时候就有必定的实现难度了!
多是为了简化相似代码的逻辑,Go
语言引入了 defer
关键字,创造了"延迟函数"的概念.测试
defer
的文件拷贝func TestCopyFileWithoutDefer(t *testing.T) { if srcFile, err := os.Open("fib.txt"); err != nil { t.Error(err) return } else { if dstFile,err := os.Create("fib.txt.bak");err != nil{ t.Error(err) return }else{ io.Copy(dstFile,srcFile) dstFile.Close() srcFile.Close() } } }
defer
的文件拷贝func TestCopyFileWithDefer(t *testing.T) { if srcFile, err := os.Open("fib.txt"); err != nil { t.Error(err) return } else { defer srcFile.Close() if dstFile, err := os.Create("fib.txt.bak"); err != nil { t.Error(err) return } else { defer dstFile.Close() io.Copy(dstFile, srcFile) } } }
上述示例代码简单展现了 defer
关键字的基本使用方式,显著的好处在于 Open/Close
是一对操做,不会由于写到最后而忘记 Close
操做,并且连续依赖时也能正常保证延迟时机.google
简而言之,若是函数内部存在连续依赖关系,也就是说建立顺序是 A->B->C
而销毁顺序是 C->B->A
.这时候使用 defer
关键字最合适不过.spa
官方文档相关表述见 Defer statements
若是没有 defer
延迟函数前,普通函数正常运行:翻译
func TestFuncWithoutDefer(t *testing.T) { // 「雪之梦技术驿站」: 正常顺序 t.Log("「雪之梦技术驿站」: 正常顺序") // 1 2 t.Log(1) t.Log(2) }
当添加 defer
关键字实现延迟后,原来的 1
被推迟到 2
后面而不是以前的 1 2
顺序.
func TestFuncWithDefer(t *testing.T) { // 「雪之梦技术驿站」: 正常顺序执行完毕后才执行 defer 代码 t.Log(" 「雪之梦技术驿站」: 正常顺序执行完毕后才执行 defer 代码") // 2 1 defer t.Log(1) t.Log(2) }
若是存在多个 defer
关键字,执行顺序可想而知,越日后的越先执行,这样才能保证按照依赖顺序依次释放资源.
func TestFuncWithMultipleDefer(t *testing.T) { // 「雪之梦技术驿站」: 猜想 defer 底层实现数据结构多是栈,先进后出. t.Log(" 「雪之梦技术驿站」: 猜想 defer 底层实现数据结构多是栈,先进后出.") // 3 2 1 defer t.Log(1) defer t.Log(2) t.Log(3) }
相信你已经明白了多个 defer
语句的执行顺序,那就测试一下吧!
func TestFuncWithMultipleDeferOrder(t *testing.T) { // 「雪之梦技术驿站」: defer 底层实现数据结构相似于栈结构,依次倒叙执行多个 defer 语句 t.Log(" 「雪之梦技术驿站」: defer 底层实现数据结构相似于栈结构,依次倒叙执行多个 defer 语句") // 2 3 1 defer t.Log(1) t.Log(2) defer t.Log(3) }
初步认识了 defer
延迟函数的使用状况后,咱们再结合文档详细解读一下相关定义.
A " defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns,either because the surrounding function executed a return statement,reached the end of its function body,or because the corresponding goroutine is panicking.
" defer"语句调用一个函数,该函数的执行 被推迟到周围函数返回的那一刻,这是由于 周围函数执行了一个 return语句,到达了函数体的 末尾,或者是由于相应的协程正在 惊慌.
具体来讲,延迟函数的执行时机大概分为三种状况:
because the surrounding function executed a return statement
return
后面的 t.Log(4)
语句天然是不会运行的,程序最终输出结果为 3 2 1
说明了 defer
语句会在周围函数执行 return
前依次逆序执行.
func funcWithMultipleDeferAndReturn() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) return fmt.Println(4) } func TestFuncWithMultipleDeferAndReturn(t *testing.T) { // 「雪之梦技术驿站」: defer 延迟函数会在包围函数正常return以前逆序执行. t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数正常return以前逆序执行.") // 3 2 1 funcWithMultipleDeferAndReturn() }
reached the end of its function body
周围函数的函数体运行到结尾前逆序执行多个 defer
语句,即先输出 3
后依次输出 2 1
.
最终函数的输出结果是 3 2 1
,也就说是没有 return
声明也能保证结束前执行完 defer
延迟函数.
func funcWithMultipleDeferAndEnd() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) } func TestFuncWithMultipleDeferAndEnd(t *testing.T) { // 「雪之梦技术驿站」: defer 延迟函数会在包围函数到达函数体结尾以前逆序执行. t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数到达函数体结尾以前逆序执行.") // 3 2 1 funcWithMultipleDeferAndEnd() }
because the corresponding goroutine is panicking
周围函数万一发生 panic
时也会先运行前面已经定义好的 defer
语句,而 panic
后续代码由于没有特殊处理,因此程序崩溃了也就没法运行.
函数的最终输出结果是 3 2 1 panic
,如此看来 defer
延迟函数仍是很是尽忠职守的,虽然内心很慌但仍是能保证老弱病残先行撤退!
func funcWithMultipleDeferAndPanic() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) panic("panic") fmt.Println(4) } func TestFuncWithMultipleDeferAndPanic(t *testing.T) { // 「雪之梦技术驿站」: defer 延迟函数会在包围函数panic惶恐不安以前逆序执行. t.Log(" 「雪之梦技术驿站」: defer 延迟函数会在包围函数panic惶恐不安以前逆序执行.") // 3 2 1 funcWithMultipleDeferAndPanic() }
经过解读 defer
延迟函数的定义以及相关示例,相信已经讲清楚什么是 defer
延迟函数了吧?
简单地说,延迟函数就是一种未雨绸缪的规划机制,帮助开发者编程程序时及时作好收尾善后工做,提早作好预案以准备随时应对各类状况.
panic
惶恐不安时,程序存在延迟函数也不会忘记执行,提早作好预案发挥了做用.因此不管是正常运行仍是异常运行,提早作好预案老是没错的,基本上能够保证万无一失,因此不妨考虑考虑 defer
延迟函数?
基本上成双成对的操做均可以使用延迟函数,尤为是申请的资源先后存在依赖关系时更应该使用 defer
关键字来简化处理逻辑.
下面举两个常见例子来讲明延迟函数的应用场景.
文件操做通常会涉及到打开和开闭操做,尤为是文件之间拷贝操做更是有着严格的顺序,只须要按照申请资源的顺序紧跟着defer
就能够知足资源释放操做.
func readFileWithDefer(filename string) ([]byte, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() return ioutil.ReadAll(f) }
锁的申请和释放是保证同步的一种重要机制,须要申请多个锁资源时可能存在依赖关系,不妨尝试一下延迟函数!
var mu sync.Mutex var m = make(map[string]int) func lookupWithDefer(key string) int { mu.Lock() defer mu.Unlock() return m[key] }
defer
延迟函数是保障关键逻辑正常运行的一种机制,若是存在多个延迟函数的话,通常会按照逆序的顺序运行,相似于栈结构.
延迟函数的运行时机通常有三种状况:
func funcWithMultipleDeferAndReturn() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) return fmt.Println(4) }
func funcWithMultipleDeferAndEnd() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) }
func funcWithMultipleDeferAndPanic() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) panic("panic") fmt.Println(4) }
本文主要介绍了什么是 defer
延迟函数,经过解读官方文档并配套相关代码认识了延迟函数,可是延迟函数中存在一些可能使人比较迷惑的地方.
读者不妨看一下下面的代码,将内心的猜测和实际运行结果比较一下,咱们下次再接着分享,感谢你的阅读.
func deferFuncWithAnonymousReturnValue() int { var retVal int defer func() { retVal++ }() return 0 } func deferFuncWithNamedReturnValue() (retVal int) { defer func() { retVal++ }() return 0 }
若是本文对你有所帮助,不用赞扬,也没必要转发,直接点赞留言告诉鼓励一下就能够啦!