網站首頁 編程語言 正文
文章目錄
- 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,main函數有兩個局部變量f1,f2有。一個返回值
2
,
c
r
e
a
t
e
函
數
有
一
個
局
部
變
量
c
=
2
\color{#A0A}{2,create函數有一個局部變量c=2}
2,create函數有一個局部變量c=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
- 上一篇:go實現分布式鎖
- 下一篇:k8s之client-go的工作邏輯
相關推薦
- 2023-12-10 記錄一次多數據源配置失效的情況
- 2022-06-02 Z-Order加速Hudi大規模數據集方案分析_服務器其它
- 2023-02-05 詳解Golang中Context的三個常見應用場景_Golang
- 2022-05-08 python添加列表元素append(),extend()及?insert()_python
- 2021-12-01 一篇文章了解c++中的new和delete_C 語言
- 2023-12-25 前后端驗證碼分析(字母&計算)
- 2022-09-22 Pod 生命周期、重啟策略、健康檢查、服務可用性檢查
- 2022-10-10 C++超詳細分析type_traits_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支