網站首頁 編程語言 正文
前言
在前邊的幾篇文章中已經基本分享完了編譯器前端的一些工作,后邊的幾篇主要是關于編譯器對抽象語法樹進行分析和重構,然后完成一系列的優化,其中包括以下五個部分:
- 變量捕獲
- 函數內聯
- 逃逸分析
- 閉包重寫
- 遍歷函數
后邊的五篇文章主要就是上邊這五個主題,本文分享的是變量捕獲,變量捕獲主要是針對閉包場景的,因為閉包函數中可能引用閉包外的變量,因此變量捕獲需要明確在閉包中通過值引用或地址引用的方式來捕獲變量
變量捕獲概述
下邊通過一個示例來看一下什么是變量捕獲
package main import ( "fmt" ) func main() { a := 1 b := 2 go func() { //在閉包里對a或b進行了重新賦值,也會改變引用方式 fmt.Println(a, b) }() a = 666 }
我們可以看到在閉包中引用了外部的變量a、b,由于變量a在閉包之后進行了其他賦值操作,因此在閉包中,a、b變量的引用方式會有所不同。在閉包中,必須采取地址引用的方式對變量a進行操作,而對變量b的引用將通過直接值傳遞的方式進行
我們可以通過如下方式查看當前程序閉包變量捕獲的情況
go tool compile -m=2 main.go | grep capturing
assign=true代表變量a在閉包完成后又進行了賦值操作
也可以看一個稍微復雜的
func adder() func(int) int {//累加器 sum := 0 //地址引用 return func(v int) int { sum += v return sum } } func main() { a := adder() for i:=0;i<10;i++{ fmt.Printf("0 + 1 + ... + %d = %d\n", i, a(i)) } }
上一篇文章分享了類型檢查,我們可以繼續順著編譯的入口文件中類型檢查后邊的代碼往下看,你會看到如下這段代碼
編譯入口文件:src/cmd/compile/main.go -> gc.Main(archInit) // Phase 4: Decide how to capture closed variables.(決定如何捕獲閉包變量) // This needs to run before escape analysis, // because variables captured by value do not escape.(變量捕獲應該在逃逸分析之前進行,因為值類型的變量捕獲,不會進行逃逸分析) timings.Start("fe", "capturevars") for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { //函數需要是閉包類型 Curfn = n capturevars(n) } } capturevarscomplete = true
從上邊這段代碼及注釋中,我們可以得到以下幾個信息:
- 變量捕獲應該在逃逸分析之前進行,因為值類型的變量捕獲,不會進行逃逸分析
- 變量捕獲是針對閉包函數的
- 變量捕獲的實現主要是調用了:src/cmd/compile/internal/gc/closure.go→
capturevars
下邊我們就去看capturevars
方法的內部實現,了解變量捕獲的一些細節
變量捕獲底層實現
所有類型檢查完成后,capturevars將在單獨的階段調用,它決定閉包捕獲的每個變量是通過值還是通過引用捕獲
func capturevars(xfunc *Node) { ...... clo := xfunc.Func.Closure cvars := xfunc.Func.Cvars.Slice() out := cvars[:0] for _, v := range cvars { ...... out = append(out, v) ...... outer := v.Name.Param.Outer outermost := v.Name.Defn // out parameters will be assigned to implicitly upon return. if outermost.Class() != PPARAMOUT && !outermost.Name.Addrtaken() && !outermost.Name.Assigned() && v.Type.Width <= 128 { v.Name.SetByval(true) } else { outermost.Name.SetAddrtaken(true) outer = nod(OADDR, outer, nil) } ...... outer = typecheck(outer, ctxExpr) clo.Func.Enter.Append(outer) } xfunc.Func.Cvars.Set(out) lineno = lno }
該方法的代碼量很少,大致內容就是,它會先獲取到閉包函數內所有變量節點,然后對這些節點進行遍歷。確定該閉包需要捕獲的變量之后再沒有被修改時,且該變量小于128字節,則會認為他是值引用。后邊它會對外部引用的結點進行類型檢查
總結
本部分比較簡單,但是挺實用的,特別是我這種一直搞不明包閉包引用外部變量的人。后邊的逃逸分析、閉包重寫跟變量捕獲有一定的聯系,介紹的后邊內容的時候再提
原文鏈接:https://juejin.cn/post/7127831298751594526
相關推薦
- 2023-06-18 一文告訴你大神是如何學習Go語言之make和new_Golang
- 2022-10-01 C語言實現學生個人消費管理系統_C 語言
- 2022-09-05 Redis的數據刪除策略
- 2022-10-14 yum-config-manager 命令找不到的解決方法
- 2022-05-03 Android?Compose自定義TextField實現自定義的輸入框_Android
- 2022-12-22 Python?Flask框架實現Proteus仿真Arduino與網頁數據交互_python
- 2022-05-15 實例詳解Python的進程,線程和協程_python
- 2023-03-05 Golang中tinyrpc框架的源碼解讀詳解_Golang
- 最近更新
-
- 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同步修改后的遠程分支