網(wǎng)站首頁 編程語言 正文
文章目錄
- 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 有。一個返回值}
1,main函數(shù)有兩個局部變量f1,f2有。一個返回值
2
,
c
r
e
a
t
e
函
數(shù)
有
一
個
局
部
變
量
c
=
2
\color{#A0A}{2,create函數(shù)有一個局部變量c=2}
2,create函數(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
相關(guān)推薦
- 2022-05-09 pytorch中的廣播語義_python
- 2022-09-04 C++?實(shí)現(xiàn)單鏈表創(chuàng)建、插入和刪除_C 語言
- 2022-08-16 Docker中部署Redis集群與部署微服務(wù)項目的詳細(xì)過程_docker
- 2022-09-08 Go語言怎么使用變長參數(shù)函數(shù)_Golang
- 2023-03-16 淺析Kotlin使用infix函數(shù)構(gòu)建可讀語法流程講解_Android
- 2022-06-01 Snort中pcre和正則表達(dá)式的使用詳解_正則表達(dá)式
- 2022-08-29 Python通用驗證碼識別OCR庫ddddocr的安裝使用教程_python
- 2022-02-16 mac 使用launchctl 開機(jī)時加速vim、emacs
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支