日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

golang defer,func()閉包,panic ,recover,contex

作者:charlie_wang007 更新時間: 2022-07-03 編程語言

文章目錄

    • defer的底層邏輯
      • 1 defer鏈表
      • 2 defer結(jié)構(gòu)體
      • 3 defer函數(shù)注冊/函數(shù)棧幀
      • 捕獲的變量值被修改/參數(shù)逃逸
      • defer嵌套
      • 4 defer和return
    • panic和recover
      • panic被recover恢復(fù)
    • go fun( )閉包
      • 1.變量值未被修改:
      • 2變量值被修改
      • 返回值是閉包 捕獲了自己
    • panic和recover
    • context

defer的底層邏輯

1 defer鏈表

在這里插入圖片描述
每一個gotoutine在runtime.g結(jié)構(gòu)體中都包含了*defer指針字段,指向defer鏈表的頭,而defer鏈表是從頭部插入(先進(jìn)后出)
在這里插入圖片描述
1deferproc在編譯的時候注冊defer函數(shù),
2函數(shù)返回之前,通過returndefer執(zhí)行注冊的defer函數(shù),再return(先注冊–返回前執(zhí)行)

2 defer結(jié)構(gòu)體

在這里插入圖片描述
defer結(jié)構(gòu)體

3 defer函數(shù)注冊/函數(shù)棧幀

在這里插入圖片描述
1棧幀先是函數(shù)A的兩個局部變量a,b
2然后是A1的參數(shù)a=1,deferproc注冊的fn地址以及參數(shù)siz
在這里插入圖片描述

3 deferproc在堆上注冊結(jié)構(gòu)體,參數(shù)a=1(若函數(shù)有返回值的話還有返回值),link和panic為nil,調(diào)用者fn=addr2,返回地址addr,調(diào)用者棧指針sp of A, started為false,參數(shù)占8字節(jié),這個結(jié)構(gòu)體會添加到defer鏈表頭,表頭的defer先執(zhí)行
在這里插入圖片描述
4 執(zhí)行defer,將defer結(jié)構(gòu)體的參數(shù)和返回值拷貝到棧上,打印參數(shù)a=1
在這里插入圖片描述

(編譯階段,deferproc將defer參數(shù)a=1拷貝到堆上,執(zhí)行時拷貝到棧上defer的參數(shù)空間,注意區(qū)別defer的參數(shù)空間與函數(shù)A的局部變量)

先進(jìn)后出

func Defer(){
	for i:=0;i<5;i++{
		fmt.Println(i)
	}
}
defer : 4
defer : 3
defer : 2
defer : 1
defer : 0

先進(jìn)后出
遇到panic ,先defer后panic

func defer_call() {
	defer func() { fmt.Println("打印前") }()
	defer func() { fmt.Println("打印中") }()
	defer func() { fmt.Println("打印后") }()

	panic("觸發(fā)異常")
}
打印后
打印中
打印前
panic: 觸發(fā)異常

defer語句包含函數(shù)的 先執(zhí)行函數(shù) 再defer
在這里插入圖片描述

在這里插入圖片描述

捕獲的變量值被修改/參數(shù)逃逸

defer捕獲外部變量形成閉包,因為參數(shù)被修改,參數(shù)a在編譯階段就改為堆分配,棧上存地址,因為要表現(xiàn)閉包內(nèi)部和外部操作同一參數(shù):
在這里插入圖片描述

在這里插入圖片描述

1 參數(shù)a除了初始化還被修改過,所以逃逸到堆上,棧上只存地址,funcval也只存地址
2 第一步輸出3,2,第二部閉包相加相加的時候,addr2指向堆上的地址=3,3+2=5,更新為5,閉包輸出棧上局部變量a指向的地址,此時存儲的是5

eg:deferproc(沒有匿名函數(shù)func() ,不涉及閉包)
在這里插入圖片描述
1deferproc注冊A時a=1,A先要拿到B 的返回值,B將a的值+1返回給A,A再+1,等于3
在這里插入圖片描述
go build -gcflags ‘-m -l’ main.go 分析上面代碼,出了io,變量a確實(shí)沒有逃逸
1,defer捕獲的變量a=1
2,函數(shù)B加1后返回給A

defer嵌套

在這里插入圖片描述
1 defer鏈表存儲A2-A1,執(zhí)行鏈表時會判斷defer是不是自己創(chuàng)建的,判斷依據(jù)為defer結(jié)構(gòu)體的sp of字段
在這里插入圖片描述
2 A2執(zhí)行,創(chuàng)建兩個defer,添加到鏈表頭部,
在這里插入圖片描述
3 執(zhí)行b2,執(zhí)行后刪除鏈表
在這里插入圖片描述

4執(zhí)行完b1,A2函數(shù)檢測到下一個鏈表的defersp不等于自己的sp,A2退出,再次回到A的執(zhí)行流程

4 defer和return

func main() {

	fmt.Println("user1",DeferFunc1(1))
	fmt.Println("user2",DeferFunc2(1))
	fmt.Println("user3",DeferFunc3(1))
}

func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		fmt.Println("d1",t)
		t += 3
	}()
	fmt.Println("r1",t)
	return t
}

func DeferFunc2(i int) int {
	t := i
	defer func() {
		fmt.Println("d2",t)
		t += 3
	}()
	fmt.Println("r2",t)

	return t
}

func DeferFunc3(i int) (t int) {
	defer func() {
		fmt.Println("d3",t)
		t += i
	}()
	fmt.Println("r3",t)

	return 2
}

執(zhí)行順序

r1 1
d1 1
user1 4
r2 1
d2 1
user2 1
r3 0
d3 2
user3 3

panic和recover

在這里插入圖片描述
panic和defer一樣,在每一個goroutine中包含panic字段,panic是一個鏈表,從頭部插入
在這里插入圖片描述
函數(shù)運(yùn)行時先將A1,A2壓入棧,執(zhí)行到panic后面的代碼就不會再執(zhí)行,協(xié)程進(jìn)入執(zhí)行defer
在這里插入圖片描述
panic觸發(fā)defer執(zhí)行時,先將defer結(jié)構(gòu)體的started設(shè)置成true,panic設(shè)置成當(dāng)前觸發(fā)的panic,即panicA
在這里插入圖片描述
按鏈表順序先執(zhí)行A2,修改struct的值,執(zhí)行完A2將其從defer鏈表移除,繼續(xù)順著鏈表執(zhí)行A1
在這里插入圖片描述
1 執(zhí)行A1,先將struct中started字段設(shè)置為true,panic字段設(shè)置為自己(panicA),執(zhí)行函數(shù)A1遇到panicA1,后面的的代碼不在執(zhí)行
2將panicA1插入panic鏈表頭,panicA1再去執(zhí)行defer鏈表,發(fā)現(xiàn)A1已開始執(zhí)行,并且 panic字段并不是自己,而是panicA(標(biāo)defer已經(jīng)被panicA執(zhí)行過了,要將其移除)
3 panicA1根據(jù)記錄的指針找到panicA,把他aborted標(biāo)記為true,deferA1被釋放

在這里插入圖片描述
4,defer列表為空,執(zhí)行panic輸出,從后向前執(zhí)行
panic結(jié)構(gòu)體:
在這里插入圖片描述
在這里插入圖片描述
recover字段只把panic結(jié)構(gòu)體的recover字段設(shè)置為true

panic被recover恢復(fù)

在這里插入圖片描述
1 發(fā)生panic后執(zhí)行A2,發(fā)現(xiàn)recover,把panicA置為已恢復(fù),recover的任務(wù)就完成了,程序繼續(xù)執(zhí)行
2 A2執(zhí)行完,檢查panic以恢復(fù),從鏈表移除,deferA2移除,執(zhí)行A1,程序結(jié)束
在這里插入圖片描述
接著上面的例子,來看什么情況下panic不會被釋放
在這里插入圖片描述
1,當(dāng)defer A2打印后又發(fā)生panic,先將panicA2插入panic列表頭部,
2,再執(zhí)行deferA2,發(fā)現(xiàn)A2已執(zhí)行,將A2從列表移除,執(zhí)行deferA1
在這里插入圖片描述
3 A1執(zhí)行完清除defer列表,打印panic信息
4 先打印painc A,打印時要打印recover信息,再打印panicA2,程序退出

在這里插入圖片描述
1 函數(shù)A先注冊一個panicA ,注冊兩個defer A1和A2,發(fā)生panic后調(diào)用gopanic ,將panicA加入panic鏈表,執(zhí)行A2
2 函數(shù)A2注冊defer B1插入鏈表頭,注冊panicA2插入鏈表頭,開始執(zhí)行deferB1
3 函數(shù)B1恢復(fù)了panic鏈表頭部的panicA2,B1正常結(jié)束

恢復(fù)過程:
1 B1的recover恢復(fù)了panicA2,他要將panic2的recover字段置為true,然后跳出出并移除當(dāng)前panic

在這里插入圖片描述

在這里插入圖片描述

go fun( )閉包

1閉包函數(shù)內(nèi)部可以操作外部定義的局部變量
2閉包離開上下文環(huán)境也可以執(zhí)行

閉 包 捕 獲 了 局 部 變 量 與 閉 包 函 數(shù) 生 產(chǎn) 了 f u n c c a l 結(jié) 構(gòu) 體 , 存 儲 在 棧 上 \color{#A0A}{閉包捕獲了局部變量與閉包函數(shù)生產(chǎn)了funccal結(jié)構(gòu)體,存儲在棧上} 數(shù)產(chǎn)funccal結(jié)構(gòu)

3閉包函數(shù)代碼在編譯階段生成在堆上代碼區(qū),因為要捕獲變量,閉包結(jié)構(gòu)體在函數(shù)執(zhí)行時生成
4捕獲變量的值未被修改,捕獲值拷貝,若被修改,則捕獲變量的地址

go語言函數(shù)可以作為參數(shù),返回值,也可以綁定到變量
在這里插入圖片描述
這樣的函數(shù)稱為function value 它是一個結(jié)構(gòu)體,里面存儲的也是函數(shù)入口地址
在這里插入圖片描述
函數(shù)運(yùn)行是在堆上分配一個地址存儲fn,指向函數(shù)入口

在這里插入圖片描述

閉包捕獲對外部變量是通過引用的方式實(shí)現(xiàn)的;會隨著外部變量的改變而修改。為避免此問題可:
通過參數(shù)方式傳入外部變量;
定義局部變量的方式;

func delayPrint() {
    // 通過參數(shù)方式保證每個變量值是不同的;
	for i := 0; i < 3; i++ {
		go func(i int) {
			time.Sleep(time.Second * 1)
			fmt.Println("By param: ", i)
		}(i)
	}
	time.Sleep(time.Second * 4)
	
    // 直接引用外部變量,會發(fā)現(xiàn)所有調(diào)用最終都捕獲了同一個變量值
	for i := 0; i < 3; i++ {
		go func() {
			time.Sleep(time.Second * 1)
			fmt.Println("By clouser: ", i)
		}()
	}
	time.Sleep(time.Second * 4)

    // 通過引入局部變量方式,保證捕獲的變量是不同的
	for i := 0; i < 3; i++ {
		tmp := i
		go func() {
			time.Sleep(time.Second * 1)
			fmt.Println("By tmp: ", tmp)
		}()
	}
	time.Sleep(time.Second * 4)
}

// By param:  2
// By param:  0
// By param:  1
// By clouser:  3
// By clouser:  3
// By clouser:  3
// By tmp:  0
// By tmp:  2
// By tmp:  1
————————————————

1.變量值未被修改:

捕獲變量c對應(yīng)兩個地址
閉包函數(shù)對應(yīng)一個地址
在這里插入圖片描述
1 , m a i n 函 數(shù) 有 兩 個 局 部 變 量 f 1 , f 2 有 。 一 個 返 回 值 \color{#A0A}{1,main函數(shù)有兩個局部變量f1,f2 有。一個返回值} 1main數(shù)f1f2
2 , c r e a t e 函 數(shù) 有 一 個 局 部 變 量 c = 2 \color{#A0A}{2,create函數(shù)有一個局部變量c=2} 2create數(shù)c=2
3 , 函 數(shù) 運(yùn) 行 時 在 堆 上 創(chuàng) 建 f u n c t i o n v a l u e 結(jié) 構(gòu) 體 , 包 含 閉 包 入 口 地 址 和 閉 包 捕 獲 變 量 c \color{#A0A}{3,函數(shù)運(yùn)行時在堆上創(chuàng)建function value結(jié)構(gòu)體,包含閉包入口地址和閉包捕獲變量c} 3數(shù)運(yùn)創(chuàng)functionvalue結(jié)構(gòu)c

2變量值被修改

在這里插入圖片描述
main的局部變量fs是長度為2的數(shù)組
有兩個返回值
c r e a t e 函 數(shù) 的 局 部 變 量 因 為 被 捕 獲 所 以 在 堆 上 分 配 \color{#A0A}{create函數(shù)的局部變量因為被捕獲所以在堆上分配} create數(shù)

在這里插入圖片描述
在這里插入圖片描述
1兩次循環(huán)閉包的捕獲變量存儲的都是地址,for執(zhí)行完i的值是2,然后這個地址寫入了返回值
2返回值拷貝給fs的時候,這個捕獲變量i地址指向的值就是2
3調(diào)用fs[i]的時候,addr0,addr1一次寫入寄存器,兩個閉包捕獲變量地址指向同一個位置,值是2

在這里插入圖片描述

返回值是閉包 捕獲了自己

在這里插入圖片描述
1main調(diào)用x時 x有一個返回值,返回值是funcval
2 func x返回值捕獲了y,所以y是有捕獲列表的funcval
3 因為捕獲的變量y一開始定義了func,return時又做了修改,所以捕獲了地址
在這里插入圖片描述
4 當(dāng)main調(diào)用x()返回y時,y保存在堆上,返回值 這里就變成了y的地址&y,這與func x()的定義不符,
在這里插入圖片描述
5 這樣編譯器會在堆上保存y的副本y’,同時為x生成一個局部變量py‘記錄y’的地址
在這里插入圖片描述
6 這樣只需要在return時將y’的值拷貝到返回值空間,y’和y都指向的是堆上的funcval fn
在這里插入圖片描述
調(diào)用y指向的就是y’,捕獲的變量也是y‘’,輸出z

panic和recover

在這里插入圖片描述
recover捕捉panic信息,panic之前壓入棧的defer會執(zhí)行,沒有入棧的不執(zhí)行
在這里插入圖片描述
defer按先入后出執(zhí)行 recover捕捉第一個panic

context

Context 的結(jié)構(gòu)非常簡單,它是一個接口。
在這里插入圖片描述

在這里插入圖片描述
timerctx,封裝的timer和deadline的cancelctx,定時或在deadline時取消ctx
在這里插入圖片描述
當(dāng)然會涉及到節(jié)點(diǎn)管理
在這里插入圖片描述

Context 提供跨越API的截止時間獲取,取消信號,以及請求范圍值的功能。//
它的這些方案在多個 goroutine 中使用是安全的

  type Context interface {
   // 如果設(shè)置了截止時間,這個方法ok會是true,并返回設(shè)置的截止時間 
   Deadline() (deadline time.Time, ok bool)
    // 如果 Context 超時或者主動取消返回一個關(guān)閉的channel,如果返回的是nil,表示這個 
    // context 永遠(yuǎn)不會關(guān)閉,比如:Background() 
    Done() <-chan struct{} 
    // 返回發(fā)生的錯誤 
    Err() error
     // 它的作用就是傳值
     Value(key interface{}) interface{}
     }
 寫到這里,我們打住想一想,如果你來實(shí)現(xiàn)這樣一個能力的 package,你抽象的接口是否也是具備這樣四個能力?

獲取截止時間
獲取信號
獲取信號產(chǎn)生的對應(yīng)錯誤信息
傳值專用

原文鏈接:https://blog.csdn.net/weixin_41479678/article/details/125498906

欄目分類
最近更新