Go語(yǔ)言編譯原理之變量捕獲
前言
在前邊的幾篇文章中已經(jīng)基本分享完了編譯器前端的一些工作,后邊的幾篇主要是關(guān)于編譯器對(duì)抽象語(yǔ)法樹(shù)進(jìn)行分析和重構(gòu),然后完成一系列的優(yōu)化,其中包括以下五個(gè)部分:
- 變量捕獲
- 函數(shù)內(nèi)聯(lián)
- 逃逸分析
- 閉包重寫(xiě)
- 遍歷函數(shù)
后邊的五篇文章主要就是上邊這五個(gè)主題,本文分享的是變量捕獲,變量捕獲主要是針對(duì)閉包場(chǎng)景的,因?yàn)殚]包函數(shù)中可能引用閉包外的變量,因此變量捕獲需要明確在閉包中通過(guò)值引用或地址引用的方式來(lái)捕獲變量
變量捕獲概述
下邊通過(guò)一個(gè)示例來(lái)看一下什么是變量捕獲
package main import ( "fmt" ) func main() { a := 1 b := 2 go func() { //在閉包里對(duì)a或b進(jìn)行了重新賦值,也會(huì)改變引用方式 fmt.Println(a, b) }() a = 666 }
我們可以看到在閉包中引用了外部的變量a、b,由于變量a在閉包之后進(jìn)行了其他賦值操作,因此在閉包中,a、b變量的引用方式會(huì)有所不同。在閉包中,必須采取地址引用的方式對(duì)變量a進(jìn)行操作,而對(duì)變量b的引用將通過(guò)直接值傳遞的方式進(jìn)行
我們可以通過(guò)如下方式查看當(dāng)前程序閉包變量捕獲的情況
go tool compile -m=2 main.go | grep capturing
assign=true代表變量a在閉包完成后又進(jìn)行了賦值操作
也可以看一個(gè)稍微復(fù)雜的
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)) } }
上一篇文章分享了類型檢查,我們可以繼續(xù)順著編譯的入口文件中類型檢查后邊的代碼往下看,你會(huì)看到如下這段代碼
編譯入口文件: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.(變量捕獲應(yīng)該在逃逸分析之前進(jìn)行,因?yàn)橹殿愋偷淖兞坎东@,不會(huì)進(jìn)行逃逸分析) timings.Start("fe", "capturevars") for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { //函數(shù)需要是閉包類型 Curfn = n capturevars(n) } } capturevarscomplete = true
從上邊這段代碼及注釋中,我們可以得到以下幾個(gè)信息:
- 變量捕獲應(yīng)該在逃逸分析之前進(jìn)行,因?yàn)橹殿愋偷淖兞坎东@,不會(huì)進(jìn)行逃逸分析
- 變量捕獲是針對(duì)閉包函數(shù)的
- 變量捕獲的實(shí)現(xiàn)主要是調(diào)用了:src/cmd/compile/internal/gc/closure.go→
capturevars
下邊我們就去看capturevars
方法的內(nèi)部實(shí)現(xiàn),了解變量捕獲的一些細(xì)節(jié)
變量捕獲底層實(shí)現(xiàn)
所有類型檢查完成后,capturevars將在單獨(dú)的階段調(diào)用,它決定閉包捕獲的每個(gè)變量是通過(guò)值還是通過(guò)引用捕獲
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 }
該方法的代碼量很少,大致內(nèi)容就是,它會(huì)先獲取到閉包函數(shù)內(nèi)所有變量節(jié)點(diǎn),然后對(duì)這些節(jié)點(diǎn)進(jìn)行遍歷。確定該閉包需要捕獲的變量之后再?zèng)]有被修改時(shí),且該變量小于128字節(jié),則會(huì)認(rèn)為他是值引用。后邊它會(huì)對(duì)外部引用的結(jié)點(diǎn)進(jìn)行類型檢查
總結(jié)
本部分比較簡(jiǎn)單,但是挺實(shí)用的,特別是我這種一直搞不明包閉包引用外部變量的人。后邊的逃逸分析、閉包重寫(xiě)跟變量捕獲有一定的聯(lián)系,介紹的后邊內(nèi)容的時(shí)候再提
以上就是Go語(yǔ)言編譯原理之變量捕獲的詳細(xì)內(nèi)容,更多關(guān)于Go編譯原理變量捕獲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang?RPC的原理與簡(jiǎn)單調(diào)用詳解
RPC(Remote?Procedure?Call),主要是幫助我們屏蔽網(wǎng)絡(luò)編程細(xì)節(jié)?,使我們更專注于業(yè)務(wù)邏輯,所以本文主要來(lái)和大家聊聊RPC的原理與簡(jiǎn)單調(diào)用,希望對(duì)大家有所幫助2023-05-05golang實(shí)現(xiàn)實(shí)時(shí)監(jiān)聽(tīng)文件并自動(dòng)切換目錄
這篇文章主要給大家介紹了golang實(shí)現(xiàn)實(shí)時(shí)監(jiān)聽(tīng)文件,并自動(dòng)切換目錄,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12Go語(yǔ)言HTTP請(qǐng)求流式寫(xiě)入body的示例代碼
這篇文章主要介紹了Go語(yǔ)言HTTP請(qǐng)求流式寫(xiě)入body,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Golang切片連接成字符串的實(shí)現(xiàn)示例
本文主要介紹了Golang切片連接成字符串的實(shí)現(xiàn)示例,可以使用Go語(yǔ)言中的內(nèi)置函數(shù)"String()"可以將字節(jié)切片轉(zhuǎn)換為字符串,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解
Go?1.21?版本已經(jīng)正式發(fā)布,它帶來(lái)了許多新特性和改進(jìn),其中引入了的三個(gè)新內(nèi)置函數(shù):max、min?和?clear,接下來(lái)我們就來(lái)看看這些函數(shù)的用途和特點(diǎn)吧2023-08-08