最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang defer延遲語句的實(shí)現(xiàn)

 更新時(shí)間:2024年07月29日 09:24:41   作者:hcraM41  
defer擁有注冊(cè)延遲調(diào)用的機(jī)制,本文主要介紹了Golang defer延遲語句的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、defer的簡(jiǎn)單使用

defer 擁有注冊(cè)延遲調(diào)用的機(jī)制,defer 關(guān)鍵字后面跟隨的語句或者函數(shù),會(huì)在當(dāng)前的函數(shù)return 正常結(jié)束 或者 panic 異常結(jié)束 后執(zhí)行。

但是defer 只有在注冊(cè)后,最后才能生效調(diào)用執(zhí)行,return 之后的defer 語句是不會(huì)執(zhí)行的,因?yàn)椴]有注冊(cè)成功。

如下例子:

func main() {
	defer func() {
		fmt.Println(111)
	}()

	fmt.Println(222)
	return

	defer func() {
		fmt.Println(333)
	}()
}

執(zhí)行結(jié)果:

222
111

解析:222 、111 是在return 之前注冊(cè)的,所以如期執(zhí)行,333 是在return 之后注冊(cè)的,注冊(cè)失敗,執(zhí)行不了。

defer 在需要資源釋放的場(chǎng)景非常有用,可以很方便地在函數(shù)結(jié)束前執(zhí)行一些操作。

比如在 打開連接/關(guān)閉連接 、加鎖/釋放鎖、打開文件/關(guān)閉文件 這些場(chǎng)景下:

file, err := os.Open("1.txt")
if err != nil {
    panic(err)
}
if file != nil {
    defer file.Close()
}

這里要注意的是:在調(diào)用file.Close() 之前,需要判斷file 是否為空,避免出現(xiàn)異常情況。

再來看一個(gè)錯(cuò)誤示范,沒有正確使用defer 的例子:

player.mu.Lock()
rand.Intn(number)
player.mu.Unlock()

這三行代碼,存在兩個(gè)問題:
1. 中間這行代碼 rand.Intn(number) 是有可能發(fā)生panic 的,這就會(huì)導(dǎo)致沒有正常解鎖。
2. 這樣的代碼在項(xiàng)目中后續(xù)可能被其他人修改,在rand.Intn(number) 后增加更多的邏輯,這是完全不可控的。

Lock 和 Unlock 之間的代碼一旦出現(xiàn) panic ,就會(huì)造成死鎖。因此,即使邏輯非常簡(jiǎn)單,使用defer 也是很有必要的,因?yàn)樾枨罂傇谧兓?,代碼也總會(huì)被修改。

二、defer的函數(shù)參數(shù)與閉包引用

defer 延遲語句不會(huì)馬上執(zhí)行,而是會(huì)進(jìn)入一個(gè)棧,函數(shù)return 前,會(huì)按先進(jìn)后出的順序執(zhí)行。

先進(jìn)后出的原因是后面定義的函數(shù)可能會(huì)依賴前面的資源,自然要先執(zhí)行;否則,如果前面的先執(zhí)行了,那么后面函數(shù)的依賴就沒有了,就可能會(huì)導(dǎo)致出錯(cuò)。

defer 函數(shù)定義時(shí),對(duì)外部變量的引用有三種方式:值傳參、指針傳參、閉包引用。

  • 值傳參:在defer 定義時(shí)就把值傳遞給defer ,并復(fù)制一份cache起來,defer調(diào)用時(shí)和定義的時(shí)候值是一致的。
  • 指針傳參:在defer 定義時(shí)就把指針傳遞給defer ,defer調(diào)用時(shí)根據(jù)整個(gè)上下文確定參數(shù)當(dāng)前的值。
  • 閉包引用:在defer 定義時(shí)就把值引用傳遞給defer ,defer調(diào)用時(shí)根據(jù)整個(gè)上下文確定參數(shù)當(dāng)前的值。

下面通過例子加深一下理解。

例子1:

func main() {
	var arr [4]struct{}

	for i := range arr {
		defer func() {
			fmt.Println(i)
		}()
	}
}

執(zhí)行結(jié)果:

3
3
3
3

解析:因?yàn)?code>defer 后面跟著的是一個(gè)閉包,根據(jù)整個(gè)上下文確定,for 循環(huán)結(jié)束后i 的值為3,因此最后打印了4個(gè)3。

例子2:

func main() {
	var n int

	// 值傳參
	defer func(n1 int) {
		fmt.Println(n1)
	}(n)

	// 指針傳參
	defer func(n2 *int) {
		fmt.Println(*n2)
	}(&n)

	// 閉包
	defer func() {
		fmt.Println(n)
	}()

	n = 4
}

執(zhí)行結(jié)果:

4
4
0

解析:

defer 執(zhí)行順序和定義的順序是相反的;

第三個(gè)defer 語句是閉包,引用的外部變量n ,defer調(diào)用時(shí)根據(jù)上下文確定,最終結(jié)果是4;

第二個(gè)defer 語句是指針傳參,defer調(diào)用時(shí)根據(jù)整個(gè)上下文確定參數(shù)當(dāng)前的值,最終結(jié)果是4;

第一個(gè)defer 語句是值傳參,defer調(diào)用時(shí)和定義的時(shí)候值是一致的,最終結(jié)果是0;

例子3:

func main() {
	// 文件1
	f, _ := os.Open("1.txt")
	if f != nil {
		defer func(f io.Closer) {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file err 1 %v\n", err)
			}
		}(f)
	}

	// 文件2
	f, _ = os.Open("2.txt")
	if f != nil {
		defer func(f io.Closer) {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file err 2 %v\n", err)
			}
		}(f)
	}

	fmt.Println("success")
}

執(zhí)行結(jié)果:

success

解析:先說結(jié)論,這個(gè)例子的代碼沒有問題,兩個(gè)文件都會(huì)被成功關(guān)閉。這個(gè)是對(duì)defer 原理的應(yīng)用,因?yàn)?code>defer 函數(shù)在定義的時(shí)候,參數(shù)就已經(jīng)復(fù)制進(jìn)去了,這里是值傳參,真正執(zhí)行close() 函數(shù)的時(shí)候就剛好關(guān)閉的是正確的文件。如果不把f 當(dāng)做值傳參,最后兩個(gè)close() 函數(shù)關(guān)閉的就是同一個(gè)文件了,都是最后打開的那個(gè)文件。

例子3的錯(cuò)誤示范:

func main() {
	// 文件1
	f, _ := os.Open("1.txt")
	if f != nil {
		defer func() {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file err 1 %v\n", err)
			}
		}()
	}

	// 文件2
	f, _ = os.Open("2.txt")
	if f != nil {
		defer func() {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file err 2 %v\n", err)
			}
		}()
	}

	fmt.Println("success")
}

執(zhí)行結(jié)果:

success
defer close file err 1 close 2.txt: file already closed

例子4:

// 值傳參
func func1() {
	var err error
	defer fmt.Println(err)
	err = errors.New("func1 error")
	return
}

// 閉包
func func2() {
	var err error
	defer func() {
		fmt.Println(err)
	}()
	err = errors.New("func2 error")
	return
}

// 值傳參
func func3() {
	var err error
	defer func(err error) {
		fmt.Println(err)
	}(err)
	err = errors.New("func3 error")
	return
}

// 指針傳參
func func4() {
	var err error
	defer func(err *error) {
		fmt.Println(*err)
	}(&err)
	err = errors.New("func4 error")
	return
}

func main() {
	func1()
	func2()
	func3()
	func4()
}

執(zhí)行結(jié)果:

<nil>
func2 error
<nil>
func4 error

解析:

第一個(gè)和第三個(gè)函數(shù)中,都是作為參數(shù),進(jìn)行值傳參,err 在定義的時(shí)候就會(huì)求值,因?yàn)槎x的時(shí)候值都是nil ,所以最后的結(jié)果都是nil ;

第二個(gè)函數(shù)的參數(shù)在定義的時(shí)候也求值了,但是它是個(gè)閉包,查看上下文發(fā)現(xiàn)最后值被修改為func2 error ;

第四個(gè)函數(shù)是指針傳參,最后值被修改為func4 error ;

現(xiàn)實(shí)中,第三個(gè)函數(shù)閉包的例子是比較容易犯的錯(cuò)誤,導(dǎo)致最后defer 語句沒有起到作用,造成生產(chǎn)上的事故,需要特別注意。

三、defer的語句拆解

從返回值出發(fā)來拆解延遲語句 defer 。

return xxx

這條語句經(jīng)過編譯之后,實(shí)際上生成了三條指令:

1. 返回值 = xxx
2. 調(diào)用 defer 函數(shù)
3. 空的 return

其中,1 和 3 是return 語句生成的指令,2 是defer 語句生成的指令。可以看出:

return 并不是一條原子指令;defer 語句在第二步調(diào)用,這里可能操作返回值,從而影響最終結(jié)果。

接下來通過例子來加深理解。

例子1:

func func1() (r int) {
	t := 3
	defer func() {
		t = t + 3
	}()

	return t
}

func main() {
	r := func1()
	fmt.Println(r)
}

執(zhí)行結(jié)果:

3

語句拆解:

func func1() (r int) {
	t := 3

	// 1.返回值=xxx:賦值指令
	r = t

	// 2.調(diào)用defer函數(shù):defer在賦值與返回之前執(zhí)行,這個(gè)例子中返回值r沒有被修改過
	func() {
		t = t + 3
	}()

	// 3.空的return
	return
}

func main() {
	r := func1()
	fmt.Println(r)
}

解析:因?yàn)榈诙€(gè)步驟里并沒有操作返回值r ,所以最終得到的結(jié)果是3 。

例子2:

func func2() (r int) {

	defer func(r int) {
		r = r + 3
	}(r)

	return 1
}

func main() {
	r := func2()
	fmt.Println(r)
}

執(zhí)行結(jié)果:

1

語句拆解:

func func2() (r int) {

	// 1.返回值=xxx:賦值指令
	r = 1

	// 2.調(diào)用defer函數(shù):因?yàn)槭侵祩鲄?,所以修改的r是個(gè)復(fù)制的值,不會(huì)影響要返回的那個(gè)r值。
	func(r int) {
		r = r + 3
	}(r)

	// 3.空的return
	return
}

func main() {
	r := func2()
	fmt.Println(r)
}

解析:因?yàn)榈诙€(gè)步驟里改變的是傳值進(jìn)去的r 值,是一個(gè)形參的復(fù)制值,不會(huì)影響實(shí)參r ,所以最終得到的結(jié)果是1 。

例子3:

func func3() (r int) {

	defer func() {
		r = r + 3
	}()

	return 1
}

func main() {
	r := func3()
	fmt.Println(r)
}

執(zhí)行結(jié)果:

4

語句拆解:

func func3() (r int) {

	// 1.返回值=xxx:賦值指令
	r = 1

	// 2.調(diào)用defer函數(shù):因?yàn)槭情]包,捕獲的變量是引用傳遞,所以會(huì)修改返回的那個(gè)r值。
	func() {
		r = r + 3
	}()

	// 3.空的return
	return
}

func main() {
	r := func3()
	fmt.Println(r)
}

解析:因?yàn)榈诙€(gè)步驟里改變的r 值是閉包,閉包中捕獲的變量是引用傳遞,不是值傳遞,所以最終得到的結(jié)果是4 。

四、defer中的recover

代碼中的panic 最終會(huì)被recover 捕獲到。在日常開發(fā)中,可能某一條協(xié)議的邏輯觸發(fā)了某一個(gè)bug 造成panic ,這時(shí)就可以用recover 去捕獲panic ,穩(wěn)住主流程,不影響其他協(xié)議的業(yè)務(wù)邏輯。

需要注意的是,recover 函數(shù)只在defer 的函數(shù)中直接調(diào)用才生效。

通過例子看recover 調(diào)用情況。

例子1:

func func1() {
	if err := recover(); err != nil {
		fmt.Println("func1 recover", err)
		return
	}
}

func main() {
	defer func1()
	panic("func1 panic")
}

執(zhí)行結(jié)果:

func1 recover func1 panic

解析:正確recover ,因?yàn)樵?code>defer 中調(diào)用的,所以可以生效。

例子2:

func main() {
	recover()
	panic("func2 panic")
}

執(zhí)行結(jié)果:

panic: func2 panic

goroutine 1 [running]:
main.main()
        C:/Users/ycz/go/ccc.go:5 +0x31
exit status 2

解析:錯(cuò)誤recover ,直接調(diào)用recover ,返回nil 。

例子3:

func main() {
	defer recover()
	panic("func3 panic")
}

執(zhí)行結(jié)果:

panic: func3 panic

goroutine 1 [running]:
main.main()
        C:/Users/ycz/go/ccc.go:5 +0x65
exit status 2

解析:錯(cuò)誤recover ,recover 需要在defer 的函數(shù)里調(diào)用。

例子4:

func main() {
	defer func() {
		defer func() {
			recover()
		}()
	}()
	panic("func4 panic")
}

執(zhí)行結(jié)果:

panic: func4 panic

goroutine 1 [running]:
main.main()
        C:/Users/ycz/go/ccc.go:9 +0x49
exit status 2

解析:錯(cuò)誤recover ,不能在多重defer 嵌套里調(diào)用recover 。

另外需要注意的一點(diǎn)是,goroutine 無法 recover 住 子goroutine 的 panic 。

原因是,goroutine 被設(shè)計(jì)為一個(gè)獨(dú)立的代碼執(zhí)行單元,擁有自己的執(zhí)行棧,不與其他goroutine 共享任何的數(shù)據(jù)。

也就是說,無法讓goroutine 擁有返回值,也無法讓goroutine 擁有自身的ID 編號(hào)。

如果希望有一個(gè)全局的panic 捕獲中心,那么可以通過channel 來實(shí)現(xiàn),如下示例:

var panicNotifyManage chan interface{}

func StartGlobalPanicRecover() {
	panicNotifyManage = make(chan interface{})
	go func() {
		select {
		case err := <-panicNotifyManage:
			fmt.Println("panicNotifyManage--->", err)
		}
	}()
}

func GoSafe(f func()) {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				panicNotifyManage <- err
			}
		}()
		f()
	}()
}

func main() {
	StartGlobalPanicRecover()
	f1 := func() {
		panic("f1 panic")
	}
	GoSafe(f1)
	time.Sleep(time.Second)
}

解析:GoSafe() 本質(zhì)上是對(duì)go 關(guān)鍵字進(jìn)行了一層封裝,確保在執(zhí)行并發(fā)單元前插入一個(gè)defer ,從而保證能夠recover 住panic 。但是這個(gè)方案并不完美,如果開發(fā)人員不使用GoSafe 函數(shù)來創(chuàng)建goroutine ,而是自己創(chuàng)建,并且在代碼中出現(xiàn)了panic ,那么仍然會(huì)造成程序崩潰。

到此這篇關(guān)于Golang defer延遲語句的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang defer延遲語句內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入探究Golang中l(wèi)og標(biāo)準(zhǔn)庫的使用

    深入探究Golang中l(wèi)og標(biāo)準(zhǔn)庫的使用

    Go?語言標(biāo)準(zhǔn)庫中的?log?包設(shè)計(jì)簡(jiǎn)潔明了,易于上手,可以輕松記錄程序運(yùn)行時(shí)的信息、調(diào)試錯(cuò)誤以及跟蹤代碼執(zhí)行過程中的問題等。本文主要來深入探究?log?包的使用和原理,幫助讀者更好地了解和掌握它
    2023-05-05
  • golang 連接mongoDB的方法示例

    golang 連接mongoDB的方法示例

    這篇文章主要介紹了golang 連接mongoDB的方法示例,詳細(xì)的介紹了golang的基礎(chǔ)知識(shí)和連接mongoDB的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • Go疑難雜癥講解之為什么nil不等于nil

    Go疑難雜癥講解之為什么nil不等于nil

    在日常開發(fā)中,可能一不小心就會(huì)掉進(jìn)?Go?語言的某些陷阱里,而本文要介紹的?nil?≠?nil?問題,感興趣的小伙伴可以跟隨小編一起了解一下
    2022-10-10
  • 使用Go語言實(shí)現(xiàn)的WebDAV內(nèi)存文件系統(tǒng)

    使用Go語言實(shí)現(xiàn)的WebDAV內(nèi)存文件系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了如何使用Go語言實(shí)現(xiàn)的WebDAV內(nèi)存文件系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-11-11
  • 使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫

    使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫

    本文檔主要描述通過Go和Gorm實(shí)現(xiàn)生成和讀取SQLCipher加密數(shù)據(jù)庫以及其中踩的一些坑,文章通過代碼示例講解的非常詳細(xì), 對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-06-06
  • Go語言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解

    Go語言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • golang 監(jiān)聽服務(wù)的信號(hào),實(shí)現(xiàn)平滑啟動(dòng),linux信號(hào)說明詳解

    golang 監(jiān)聽服務(wù)的信號(hào),實(shí)現(xiàn)平滑啟動(dòng),linux信號(hào)說明詳解

    這篇文章主要介紹了golang 監(jiān)聽服務(wù)的信號(hào),實(shí)現(xiàn)平滑啟動(dòng),linux信號(hào)說明詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • golang-gorm自動(dòng)建表問題

    golang-gorm自動(dòng)建表問題

    這篇文章主要介紹了golang-gorm自動(dòng)建表問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Go Println和Printf的區(qū)別詳解

    Go Println和Printf的區(qū)別詳解

    這篇文章主要介紹了Go Println和Printf的區(qū)別詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang的性能優(yōu)化和調(diào)試技巧

    Golang的性能優(yōu)化和調(diào)試技巧

    在開發(fā)和部署Go語言應(yīng)用程序時(shí),性能優(yōu)化和調(diào)試是至關(guān)重要的,篇博客將介紹一些常用的Golang性能優(yōu)化和調(diào)試技巧,幫助您提升應(yīng)用程序的性能并解決潛在的問題
    2023-05-05

最新評(píng)論

威远县| 玉山县| 济宁市| 乌兰察布市| 利辛县| 广河县| 武宁县| 延庆县| 武夷山市| 朝阳县| 邓州市| 仁寿县| 曲松县| 香港 | 民权县| 宁南县| 阳朔县| 塘沽区| 华阴市| 板桥市| 瑞丽市| 秦安县| 鹤庆县| 应城市| 新疆| 澜沧| 岳阳市| 鹤山市| 突泉县| 澄迈县| 婺源县| 中卫市| 利津县| 阿拉尔市| 桂林市| 花莲市| 民权县| 惠州市| 富宁县| 兰西县| 衡山县|