溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

go語言中的defer延遲函數(shù)是什么

發(fā)布時(shí)間:2021-06-29 09:58:14 來源:億速云 閱讀:148 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“go語言中的defer延遲函數(shù)是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

Go 語言中有個(gè) defer 關(guān)鍵字,常用于實(shí)現(xiàn)延遲函數(shù)來保證關(guān)鍵代碼的最終執(zhí)行,常言道: "未雨綢繆方可有備無患".

延遲函數(shù)就是這么一種機(jī)制,無論程序是正常返回還是異常報(bào)錯(cuò),只要存在延遲函數(shù)都能保證這部分關(guān)鍵邏輯最終執(zhí)行,所以用來做些資源清理等操作再合適不過了.

出入成雙有始有終

日常開發(fā)編程中,有些操作總是成雙成對出現(xiàn)的,有開始就有結(jié)束,有打開就要關(guān)閉,還有一些連續(xù)依賴關(guān)系等等.

一般來說,我們需要控制結(jié)束語句,在合適的位置和時(shí)機(jī)控制結(jié)束語句,手動保證整個(gè)程序有始有終,不遺漏清理收尾操作.

最常見的拷貝文件操作大致流程如下:

  1. 打開源文件

srcFile, err := os.Open("fib.txt")
if err != nil {
	t.Error(err)
	return
}	
  1. 創(chuàng)建目標(biāo)文件

dstFile, err := os.Create("fib.txt.bak")
if err != nil {
	t.Error(err)
	return
}
  1. 拷貝源文件到目標(biāo)文件

io.Copy(dstFile, srcFile)
  1. 關(guān)閉目標(biāo)文件

dstFile.Close()
srcFile.Close()
  1. 關(guān)閉源文件

srcFile.Close()

值得注意的是: 這種拷貝文件的操作需要特別注意操作順序而且也不要忘記釋放資源,比如先打開再關(guān)閉等等!

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()
}

> 「雪之夢技術(shù)驛站」: 上述代碼邏輯還是清晰簡單的,可能不會忘記釋放資源也能保證操作順序,但是如果邏輯代碼比較復(fù)雜的情況,這時(shí)候就有一定的實(shí)現(xiàn)難度了!

可能是為了簡化類似代碼的邏輯,Go 語言引入了 defer 關(guān)鍵字,創(chuàng)造了"延遲函數(shù)"的概念.

  • 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 關(guān)鍵字的基本使用方式,顯著的好處在于 Open/Close 是一對操作,不會因?yàn)閷懙阶詈蠖?Close 操作,而且連續(xù)依賴時(shí)也能正常保證延遲時(shí)機(jī).

簡而言之,如果函數(shù)內(nèi)部存在連續(xù)依賴關(guān)系,也就是說創(chuàng)建順序是 A->B->C 而銷毀順序是 C->B->A.這時(shí)候使用 defer 關(guān)鍵字最合適不過.

懶人福音延遲函數(shù)

> 官方文檔相關(guān)表述見 Defer statements

如果沒有 defer 延遲函數(shù)前,普通函數(shù)正常運(yùn)行:

func TestFuncWithoutDefer(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: 正常順序
	t.Log("「雪之夢技術(shù)驛站」: 正常順序")

	// 1 2
	t.Log(1)
	t.Log(2)
}

當(dāng)添加 defer 關(guān)鍵字實(shí)現(xiàn)延遲后,原來的 1 被推遲到 2 后面而不是之前的 1 2 順序.

func TestFuncWithDefer(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: 正常順序執(zhí)行完畢后才執(zhí)行 defer 代碼
	t.Log(" 「雪之夢技術(shù)驛站」: 正常順序執(zhí)行完畢后才執(zhí)行 defer 代碼")

	// 2 1
	defer t.Log(1)
	t.Log(2)
}

如果存在多個(gè) defer 關(guān)鍵字,執(zhí)行順序可想而知,越往后的越先執(zhí)行,這樣才能保證按照依賴順序依次釋放資源.

func TestFuncWithMultipleDefer(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: 猜測 defer 底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)可能是棧,先進(jìn)后出.
	t.Log(" 「雪之夢技術(shù)驛站」: 猜測 defer 底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)可能是棧,先進(jìn)后出.")

	// 3 2 1
	defer t.Log(1)
	defer t.Log(2)
	t.Log(3)
}

相信你已經(jīng)明白了多個(gè) defer 語句的執(zhí)行順序,那就測試一下吧!

func TestFuncWithMultipleDeferOrder(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: defer 底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)類似于棧結(jié)構(gòu),依次倒敘執(zhí)行多個(gè) defer 語句
	t.Log(" 「雪之夢技術(shù)驛站」: defer 底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)類似于棧結(jié)構(gòu),依次倒敘執(zhí)行多個(gè) defer 語句")

	// 2 3 1
	defer t.Log(1)
	t.Log(2)
	defer t.Log(3)
}

初步認(rèn)識了 defer 延遲函數(shù)的使用情況后,我們再結(jié)合文檔詳細(xì)解讀一下相關(guān)定義.

  • 英文原版文檔

> 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"語句調(diào)用一個(gè)函數(shù),該函數(shù)的執(zhí)行被推遲到周圍函數(shù)返回的那一刻,這是因?yàn)?strong>周圍函數(shù)執(zhí)行了一個(gè)return語句,到達(dá)了函數(shù)體的末尾,或者是因?yàn)橄鄳?yīng)的協(xié)程正在驚慌.

具體來說,延遲函數(shù)的執(zhí)行時(shí)機(jī)大概分為三種情況:

周圍函數(shù)執(zhí)行return

> because the surrounding function executed a return statement

return 后面的 t.Log(4) 語句自然是不會運(yùn)行的,程序最終輸出結(jié)果為 3 2 1 說明了 defer 語句會在周圍函數(shù)執(zhí)行 return 前依次逆序執(zhí)行.

func funcWithMultipleDeferAndReturn() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	return
	fmt.Println(4)
}

func TestFuncWithMultipleDeferAndReturn(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)正常return之前逆序執(zhí)行.
	t.Log(" 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)正常return之前逆序執(zhí)行.")

	// 3 2 1
	funcWithMultipleDeferAndReturn()
}

周圍函數(shù)到達(dá)函數(shù)體

> reached the end of its function body

周圍函數(shù)的函數(shù)體運(yùn)行到結(jié)尾前逆序執(zhí)行多個(gè) defer 語句,即先輸出 3 后依次輸出 2 1. 最終函數(shù)的輸出結(jié)果是 3 2 1 ,也就說是沒有 return 聲明也能保證結(jié)束前執(zhí)行完 defer 延遲函數(shù).

func funcWithMultipleDeferAndEnd() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
}

func TestFuncWithMultipleDeferAndEnd(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)到達(dá)函數(shù)體結(jié)尾之前逆序執(zhí)行.
	t.Log(" 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)到達(dá)函數(shù)體結(jié)尾之前逆序執(zhí)行.")

	// 3 2 1
	funcWithMultipleDeferAndEnd()
}

當(dāng)前協(xié)程正驚慌失措

> because the corresponding goroutine is panicking

周圍函數(shù)萬一發(fā)生 panic 時(shí)也會先運(yùn)行前面已經(jīng)定義好的 defer 語句,而 panic 后續(xù)代碼因?yàn)闆]有特殊處理,所以程序崩潰了也就無法運(yùn)行.

函數(shù)的最終輸出結(jié)果是 3 2 1 panic ,如此看來 defer 延遲函數(shù)還是非常盡忠職守的,雖然心里很慌但還是能保證老弱病殘先行撤退!

func funcWithMultipleDeferAndPanic() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	panic("panic")
	fmt.Println(4)
}

func TestFuncWithMultipleDeferAndPanic(t *testing.T) {
	// 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)panic驚慌失措之前逆序執(zhí)行.
	t.Log(" 「雪之夢技術(shù)驛站」: defer 延遲函數(shù)會在包圍函數(shù)panic驚慌失措之前逆序執(zhí)行.")

	// 3 2 1
	funcWithMultipleDeferAndPanic()
}

通過解讀 defer 延遲函數(shù)的定義以及相關(guān)示例,相信已經(jīng)講清楚什么是 defer 延遲函數(shù)了吧?

簡單地說,延遲函數(shù)就是一種未雨綢繆的規(guī)劃機(jī)制,幫助開發(fā)者編程程序時(shí)及時(shí)做好收尾善后工作,提前做好預(yù)案以準(zhǔn)備隨時(shí)應(yīng)對各種情況.

  • 當(dāng)周圍函數(shù)正常執(zhí)行到到達(dá)函數(shù)體結(jié)尾時(shí),如果發(fā)現(xiàn)存在延遲函數(shù)自然會逆序執(zhí)行延遲函數(shù).

  • 當(dāng)周圍函數(shù)正常執(zhí)行遇到return語句準(zhǔn)備返回給調(diào)用者時(shí),存在延遲函數(shù)時(shí)也會執(zhí)行,同樣滿足善后清理的需求.

  • 當(dāng)周圍函數(shù)異常運(yùn)行不小心 panic 驚慌失措時(shí),程序存在延遲函數(shù)也不會忘記執(zhí)行,提前做好預(yù)案發(fā)揮了作用.

所以不論是正常運(yùn)行還是異常運(yùn)行,提前做好預(yù)案總是沒錯(cuò)的,基本上可以保證萬無一失,所以不妨考慮考慮 defer 延遲函數(shù)?

go語言中的defer延遲函數(shù)是什么

延遲函數(shù)應(yīng)用場景

基本上成雙成對的操作都可以使用延遲函數(shù),尤其是申請的資源前后存在依賴關(guān)系時(shí)更應(yīng)該使用 defer 關(guān)鍵字來簡化處理邏輯.

下面舉兩個(gè)常見例子來說明延遲函數(shù)的應(yīng)用場景.

  • Open/Close

文件操作一般會涉及到打開和開閉操作,尤其是文件之間拷貝操作更是有著嚴(yán)格的順序,只需要按照申請資源的順序緊跟著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)
}
  • Lock/Unlock

鎖的申請和釋放是保證同步的一種重要機(jī)制,需要申請多個(gè)鎖資源時(shí)可能存在依賴關(guān)系,不妨嘗試一下延遲函數(shù)!

var mu sync.Mutex
var m = make(map[string]int)
func lookupWithDefer(key string) int {
	mu.Lock()
	defer mu.Unlock()
	return m[key]
}

總結(jié)以及下節(jié)預(yù)告

defer 延遲函數(shù)是保障關(guān)鍵邏輯正常運(yùn)行的一種機(jī)制,如果存在多個(gè)延遲函數(shù)的話,一般會按照逆序的順序運(yùn)行,類似于棧結(jié)構(gòu).

延遲函數(shù)的運(yùn)行時(shí)機(jī)一般有三種情況:

  • 周圍函數(shù)遇到返回時(shí)

func funcWithMultipleDeferAndReturn() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	return
	fmt.Println(4)
}
  • 周圍函數(shù)函數(shù)體結(jié)尾處

func funcWithMultipleDeferAndEnd() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
}
  • 當(dāng)前協(xié)程驚慌失措中

func funcWithMultipleDeferAndPanic() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	panic("panic")
	fmt.Println(4)
}

本文主要介紹了什么是 defer 延遲函數(shù),通過解讀官方文檔并配套相關(guān)代碼認(rèn)識了延遲函數(shù),但是延遲函數(shù)中存在一些可能令人比較迷惑的地方.

go語言中的defer延遲函數(shù)是什么

讀者不妨看一下下面的代碼,將心里的猜想和實(shí)際運(yùn)行結(jié)果比較一下,我們下次再接著分享,感謝你的閱讀.

func deferFuncWithAnonymousReturnValue() int {
	var retVal int
	defer func() {
		retVal++
	}()
	return 0
}

func deferFuncWithNamedReturnValue() (retVal int) {
	defer func() {
		retVal++
	}()
	return 0
}

“go語言中的defer延遲函數(shù)是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI