golang中的defer函數(shù)理解
golang的defer
什么是defer
defer的的官方文檔:https://golang.org/ref/spec#Defer_statements
go語(yǔ)言中defer可以完成延遲功能,當(dāng)前函數(shù)執(zhí)行完成后再執(zhí)行defer的代碼塊。通過(guò)defer,我們可以在代碼中優(yōu)雅的關(guān)閉/清理代碼中所使用的變量。
defer是Go語(yǔ)言中的延遲執(zhí)行語(yǔ)句,用來(lái)添加函數(shù)結(jié)束時(shí)執(zhí)行的代碼,常用于釋放某些已分配的資源、關(guān)閉數(shù)據(jù)庫(kù)連接、斷開(kāi)socket連接、解鎖一個(gè)加鎖的資源。
Go語(yǔ)言機(jī)制擔(dān)保一定會(huì)執(zhí)行defer語(yǔ)句中的代碼。其它語(yǔ)言中也有類(lèi)似的機(jī)制,比如Java、C#語(yǔ)言里的finally語(yǔ)句,C++語(yǔ)言里的析構(gòu)函數(shù)(Destructor)可以起類(lèi)似的作用,C++語(yǔ)言機(jī)制擔(dān)保在對(duì)象被銷(xiāo)毀前一定會(huì)執(zhí)行析構(gòu)函數(shù)中的代碼。C++中的析構(gòu)函數(shù)析構(gòu)的是對(duì)象,Go中的defer析構(gòu)的是函數(shù)。
理解defer
defer什么時(shí)間執(zhí)行(defer、 return、返回值 三者的執(zhí)行順序)
defer只有在當(dāng)前函數(shù)執(zhí)行完畢后,才會(huì)執(zhí)行。描述其實(shí)不太精確
go中的return語(yǔ)句并不是原子性操作,一般是分為兩步:
- 將返回值賦值給一個(gè)變量
- 執(zhí)行RET指令
return并不是原子性操作,是通過(guò)一個(gè)變量賦值和ret指令來(lái)完成的。defer就執(zhí)行在1之后,2之前。defer的執(zhí)行順序在return之后,但是在返回值返回給調(diào)用方之前,所以使用defer可以達(dá)到修改返回值的目的。
defer、 return、返回值 三者的執(zhí)行順序是 : return 最先給返回值賦值;接著 defer 開(kāi)始執(zhí)行一些收尾工作;最后 RET 指令攜帶返回值退出函數(shù)。
package main
import (
"fmt"
)
func main() {
ret := test()
fmt.Println("test return:", ret)
}
// func test() ( int) { 這種就是匿名返回值
//返回值改為命名返回值, 具名返回值。即返回值帶有名字, 這樣我們?cè)趫?zhí)行defer的時(shí)候相當(dāng)于修改了返回值的值
func test() (i int) {
//var i int
defer func() {
i++
fmt.Println("test defer, i = ", i)
}()
return i
}
注意: 這塊驗(yàn)證使用了具名返回值
func test() (i int) {中的(i int)。 測(cè)試結(jié)果滿(mǎn)足我們預(yù)期。
編碼中,我們要特別注意, go語(yǔ)言中匿名返回值和命名返回值對(duì)defer的影響。不過(guò)一般我們都是使用命名返回值。
一個(gè)主函數(shù)擁有一個(gè)匿名的返回值,返回時(shí)使用字面值,比如返回”1”、”2”、”Hello”這樣的值,這種情況下defer語(yǔ)句是無(wú)法操作返回值的。
defer輸出的值,就是定義時(shí)的值。而不是defer真正執(zhí)行時(shí)的變量值(注意引用情況)
defer函數(shù)會(huì)在return之后被調(diào)用。那么這段函數(shù)執(zhí)行完之后,是不用應(yīng)該輸出1呢?
package main
import "fmt"
func test1() {
i := 0
defer fmt.Println(i)
i++
return
}
func main() {
test1()
}
輸出結(jié)果:0
雖然我們?cè)赿efer后面定義的是一個(gè)帶變量的函數(shù): fmt.Println(i). 但這個(gè)變量(i)在defer被聲明的時(shí)候,就已經(jīng)確定其確定的值了。
總結(jié): 因?yàn)閐efer后面的函數(shù)在入棧的時(shí)候保存的是入棧那一刻的值,而當(dāng)時(shí)i的值是0,所以后期對(duì)i修改,并不會(huì)影響棧內(nèi)函數(shù)的值。
我們?cè)倏聪乱粋€(gè)例子:
package main
import "fmt"
func test2() {
x := 10
defer func(a *int) {
fmt.Println(*a)
}(&x)
x++
}
func main() {
test2()
}
輸出結(jié)果: 11
這里為什么和前面結(jié)論不一樣呢?
這里defer后面函數(shù)入棧的時(shí)候存入的執(zhí)行變量x的指針。所以,后期x值改變的時(shí)候,輸出結(jié)果也會(huì)改變。
總結(jié): 需要注意引用情況。對(duì)于指針類(lèi)型參數(shù),規(guī)則仍然適用,只不過(guò)延遲函數(shù)的參數(shù)是一個(gè)地址值,這種情況下,defer后面的語(yǔ)句對(duì)變量的修改會(huì)影響延遲函數(shù)。
多個(gè)defer,執(zhí)行順序
package main
import (
"fmt"
)
func main() {
defer fmt.Println("main defer1")
test()
defer fmt.Println("main defer2")
}
func test() () {
defer func() {
fmt.Println("test defer1")
}()
defer func() {
fmt.Println("test defer2")
}()
}
輸出結(jié)果:
test defer2
test defer1
main defer2
main defer1
總結(jié): 后進(jìn)先出(LIFO)的順序執(zhí)行,即先出現(xiàn)的 defer 最后執(zhí)行。即:多個(gè)defer語(yǔ)句的執(zhí)行順序是逆序執(zhí)行。
defer的函數(shù)一定會(huì)執(zhí)行么?
defer是Go語(yǔ)言中的延遲執(zhí)行語(yǔ)句,用來(lái)添加函數(shù)結(jié)束時(shí)執(zhí)行的代碼,常用于釋放某些已分配的資源、關(guān)閉數(shù)據(jù)庫(kù)連接、斷開(kāi)socket連接、解鎖一個(gè)加鎖的資源。
Go語(yǔ)言機(jī)制擔(dān)保一定會(huì)執(zhí)行defer語(yǔ)句中的代碼。其它語(yǔ)言中也有類(lèi)似的機(jī)制,比如Java、C#語(yǔ)言里的finally語(yǔ)句,C++語(yǔ)言里的析構(gòu)函數(shù)(Destructor)可以起類(lèi)似的作用,C++語(yǔ)言機(jī)制擔(dān)保在對(duì)象被銷(xiāo)毀前一定會(huì)執(zhí)行析構(gòu)函數(shù)中的代碼。C++中的析構(gòu)函數(shù)析構(gòu)的是對(duì)象,Go中的defer析構(gòu)的是函數(shù)。
panic情況
網(wǎng)上demo:
package main
import (
"fmt"
)
func test1() {
fmt.Println("test")
}
func test2() {
panic(1)
}
func main() {
fmt.Println("main start")
defer test1()
test2() //造panic
fmt.Println("main end")
}
執(zhí)行結(jié)果:
main start
test
panic: 1
goroutine 1 [running]:
main.test2(...)
總結(jié): 我們發(fā)現(xiàn)正常的panic,還是會(huì)調(diào)我們的defer的,并且在會(huì)在panic之前執(zhí)行。
os.Exit情況
網(wǎng)上demo:
package main
import (
"fmt"
"os"
)
func test1() {
fmt.Println("test")
}
func main() {
fmt.Println("main start")
defer test1()
fmt.Println("main end")
os.Exit(0)
}
執(zhí)行結(jié)果:
main start
main end
總結(jié): 如果在當(dāng)前函數(shù)里是因?yàn)閳?zhí)行了os.Exit退出,而不是正常return退出或者panic退出,那程序會(huì)立即停止,被defer的函數(shù)調(diào)用不會(huì)執(zhí)行。
kill情況(Ctrl+C)
package main
import (
"fmt"
"time"
)
func test1() {
fmt.Println("test")
}
func test2() {
time.Sleep(60 * time.Second)
}
func main() {
fmt.Println("main start")
defer test1()
test2()
fmt.Println("main end")
}
執(zhí)行結(jié)果:
main start
Process finished with the exit code -1073741510 (0xC000013A: interrupted by Ctrl+C)
如上,我們test2() 睡眠時(shí)間內(nèi),點(diǎn)擊Ctrl+C,發(fā)現(xiàn)defer test1()并沒(méi)有執(zhí)行。
總結(jié):這個(gè)點(diǎn)很重要,需要我們?cè)谌粘.惓V袛鄷r(shí),留意defer是否未處理的情況。
所以一般情況下,我們程序需要捕獲這種異常中斷,在程序退出前,手動(dòng)做一些處理。
參考文獻(xiàn)
Go常見(jiàn)坑:Go語(yǔ)言里被defer的函數(shù)一定會(huì)執(zhí)行么?
參考URL: https://blog.csdn.net/perfumekristy/article/details/121343642
面試官:聽(tīng)說(shuō)你精通golang的defer?
參考URL: https://cloud.tencent.com/developer/article/2076951
到此這篇關(guān)于golang中的defer函數(shù)理解的文章就介紹到這了,更多相關(guān)golang defer函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoLang使goroutine停止的五種方法實(shí)例
goroutine是Go并行設(shè)計(jì)的核心,下面這篇文章主要給大家介紹了關(guān)于GoLang使goroutine停止的五種方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
Golang函數(shù)重試機(jī)制實(shí)現(xiàn)代碼
在編寫(xiě)應(yīng)用程序時(shí),有時(shí)候會(huì)遇到一些短暫的錯(cuò)誤,例如網(wǎng)絡(luò)請(qǐng)求、服務(wù)鏈接終端失敗等,這些錯(cuò)誤可能導(dǎo)致函數(shù)執(zhí)行失敗,這篇文章主要介紹了Golang函數(shù)重試機(jī)制實(shí)現(xiàn)代碼,需要的朋友可以參考下2024-04-04
如何組織Go代碼目錄結(jié)構(gòu)依賴(lài)注入wire使用解析
這篇文章主要為大家介紹了如何組織Go代碼目錄結(jié)構(gòu)依賴(lài)注入wire使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
詳解Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理
這篇文章主要為大家詳細(xì)介紹了Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-11-11
golang如何實(shí)現(xiàn)抓取IP地址的蜘蛛程序詳解
這篇文章主要給大家介紹了關(guān)于利用golang如何實(shí)現(xiàn)抓取IP地址的蜘蛛程序的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
Go語(yǔ)言的代碼組織結(jié)構(gòu)詳細(xì)介紹
這篇文章主要介紹了Go語(yǔ)言的代碼碼組織結(jié)構(gòu)詳細(xì)介紹,即Go語(yǔ)言源碼的文件結(jié)構(gòu),本文講解了包、main和main.main、os包等內(nèi)容,需要的朋友可以參考下2014-10-10
使用golang開(kāi)發(fā)一個(gè)curl命令行工具
這篇文章主要為大家詳細(xì)介紹了如何使用golang開(kāi)發(fā)一個(gè)簡(jiǎn)單的curl命令行工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11

