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

學無先后,達者為師

網站首頁 編程語言 正文

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

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

文章目錄

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

defer的底層邏輯

1 defer鏈表

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

2 defer結構體

在這里插入圖片描述
defer結構體

3 defer函數注冊/函數棧幀

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

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

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

先進后出

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

先進后出
遇到panic ,先defer后panic

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

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

defer語句包含函數的 先執行函數 再defer
在這里插入圖片描述

在這里插入圖片描述

捕獲的變量值被修改/參數逃逸

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

在這里插入圖片描述

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

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

defer嵌套

在這里插入圖片描述
1 defer鏈表存儲A2-A1,執行鏈表時會判斷defer是不是自己創建的,判斷依據為defer結構體的sp of字段
在這里插入圖片描述
2 A2執行,創建兩個defer,添加到鏈表頭部,
在這里插入圖片描述
3 執行b2,執行后刪除鏈表
在這里插入圖片描述

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

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
}

執行順序

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是一個鏈表,從頭部插入
在這里插入圖片描述
函數運行時先將A1,A2壓入棧,執行到panic后面的代碼就不會再執行,協程進入執行defer
在這里插入圖片描述
panic觸發defer執行時,先將defer結構體的started設置成true,panic設置成當前觸發的panic,即panicA
在這里插入圖片描述
按鏈表順序先執行A2,修改struct的值,執行完A2將其從defer鏈表移除,繼續順著鏈表執行A1
在這里插入圖片描述
1 執行A1,先將struct中started字段設置為true,panic字段設置為自己(panicA),執行函數A1遇到panicA1,后面的的代碼不在執行
2將panicA1插入panic鏈表頭,panicA1再去執行defer鏈表,發現A1已開始執行,并且 panic字段并不是自己,而是panicA(標defer已經被panicA執行過了,要將其移除)
3 panicA1根據記錄的指針找到panicA,把他aborted標記為true,deferA1被釋放

在這里插入圖片描述
4,defer列表為空,執行panic輸出,從后向前執行
panic結構體:
在這里插入圖片描述
在這里插入圖片描述
recover字段只把panic結構體的recover字段設置為true

panic被recover恢復

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

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

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

在這里插入圖片描述

在這里插入圖片描述

go fun( )閉包

1閉包函數內部可以操作外部定義的局部變量
2閉包離開上下文環境也可以執行

閉 包 捕 獲 了 局 部 變 量 與 閉 包 函 數 生 產 了 f u n c c a l 結 構 體 , 存 儲 在 棧 上 \color{#A0A}{閉包捕獲了局部變量與閉包函數生產了funccal結構體,存儲在棧上} funccal,

3閉包函數代碼在編譯階段生成在堆上代碼區,因為要捕獲變量,閉包結構體在函數執行時生成
4捕獲變量的值未被修改,捕獲值拷貝,若被修改,則捕獲變量的地址

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

在這里插入圖片描述

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

func delayPrint() {
    // 通過參數方式保證每個變量值是不同的;
	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)
	
    // 直接引用外部變量,會發現所有調用最終都捕獲了同一個變量值
	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對應兩個地址
閉包函數對應一個地址
在這里插入圖片描述
1 , m a i n 函 數 有 兩 個 局 部 變 量 f 1 , f 2 有 。 一 個 返 回 值 \color{#A0A}{1,main函數有兩個局部變量f1,f2 有。一個返回值} 1,mainf1,f2。
2 , c r e a t e 函 數 有 一 個 局 部 變 量 c = 2 \color{#A0A}{2,create函數有一個局部變量c=2} 2,createc=2
3 , 函 數 運 行 時 在 堆 上 創 建 f u n c t i o n v a l u e 結 構 體 , 包 含 閉 包 入 口 地 址 和 閉 包 捕 獲 變 量 c \color{#A0A}{3,函數運行時在堆上創建function value結構體,包含閉包入口地址和閉包捕獲變量c} 3,functionvalue,c

2變量值被修改

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

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

在這里插入圖片描述

返回值是閉包 捕獲了自己

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

panic和recover

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

context

Context 的結構非常簡單,它是一個接口。
在這里插入圖片描述

在這里插入圖片描述
timerctx,封裝的timer和deadline的cancelctx,定時或在deadline時取消ctx
在這里插入圖片描述
當然會涉及到節點管理
在這里插入圖片描述

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

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

獲取截止時間
獲取信號
獲取信號產生的對應錯誤信息
傳值專用

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

欄目分類
最近更新