日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
曹大帶我學(xué)Go之哪里來(lái)的Goexit

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)桃花源」,作者小X。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)桃花源公眾號(hào)。

創(chuàng)新互聯(lián)公司是一個(gè)技術(shù)型專業(yè)網(wǎng)站制作公司,致力于為廣大企業(yè)、創(chuàng)業(yè)者打造切實(shí)有效的PC站、WAP站、APP站點(diǎn)等企業(yè)網(wǎng)站。無(wú)論是企業(yè)宣傳的全網(wǎng)整合營(yíng)銷(xiāo)推廣、致力于營(yíng)銷(xiāo)的電商網(wǎng)站、內(nèi)容資訊分享的各行業(yè)網(wǎng)站或其他類型網(wǎng)站,我們都從網(wǎng)站前期定位分析策劃、技術(shù)架構(gòu),到網(wǎng)站界面設(shè)計(jì)、創(chuàng)意表現(xiàn)、站點(diǎn)架構(gòu)搭建以及后續(xù)訪問(wèn)監(jiān)控、維護(hù)、網(wǎng)站托管運(yùn)營(yíng)反饋建議等提供整套服務(wù)。

你好,我是小X。

曹大最近開(kāi) Go 課程了,小X 正在和曹大學(xué) Go。

這個(gè)系列會(huì)講一些從課程中學(xué)到的讓人醍醐灌頂?shù)臇|西,撥云見(jiàn)日,帶你重新認(rèn)識(shí) Go。

在學(xué)員群里,有同學(xué)在用 dlv 調(diào)試時(shí)看到了令人不解的 goexit:goexit 函數(shù)是啥,為啥 go fun(){}() 的上層是它?看著像是一個(gè)“退出”函數(shù),為什么會(huì)出現(xiàn)在最上層?

其實(shí)如果看過(guò) pprof 的火焰圖,也會(huì)經(jīng)常看到 goexit 這個(gè)函數(shù)。

我們來(lái)個(gè)例子重現(xiàn)一下:

 
 
 
 
  1. package main 
  2.  
  3. import "time" 
  4.  
  5. func main() { 
  6.  go func ()  { 
  7.   println("hello world") 
  8.  }() 
  9.   
  10.  time.Sleep(10*time.Minute) 

啟動(dòng) dlv 調(diào)試,并分別在不同的地方打上斷點(diǎn):

 
 
 
 
  1. (dlv) b a.go:5  
  2. Breakpoint 1 (enabled) set at 0x106d12f for main.main() ./a.go:5 
  3. (dlv) b a.go:6 
  4. Breakpoint 2 (enabled) set at 0x106d13d for main.main() ./a.go:6 
  5. (dlv) b a.go:7 
  6. Breakpoint 3 (enabled) set at 0x106d1a0 for main.main.func1() ./a.go:7 

執(zhí)行命令 c 運(yùn)行到斷點(diǎn)處,再執(zhí)行 bt 命令得到 main 函數(shù)的調(diào)用棧:

 
 
 
 
  1. (dlv) bt 
  2. 0  0x000000000106d12f in main.main 
  3.    at ./a.go:5 
  4. 1  0x0000000001035c0f in runtime.main 
  5.    at /usr/local/go/src/runtime/proc.go:204 
  6. 2  0x0000000001064961 in runtime.goexit 
  7.    at /usr/local/go/src/runtime/asm_amd64.s:1374 

它的上一層是 runtime.main,找到原代碼位置,位于 src/runtime/proc.go 里的 main 函數(shù),它是 Go 進(jìn)程的 main goroutine,這里會(huì)執(zhí)行一些 init 操作、開(kāi)啟 GC、執(zhí)行用戶 main 函數(shù)……

 
 
 
 
  1. fn := main_main // proc.go:203 
  2. fn() // proc.go:204 

其中 fn 是 main_main 函數(shù),表示用戶的 main 函數(shù),執(zhí)行到了這里,才真正將權(quán)力交給用戶。

繼續(xù)執(zhí)行 c 命令和 bt 命令,得到 go 這一行的調(diào)用棧:

 
 
 
 
  1. 0  0x000000000106d13d in main.main 
  2.    at ./a.go:6 
  3. 1  0x0000000001035c0f in runtime.main 
  4.    at /usr/local/go/src/runtime/proc.go:204 
  5. 2  0x0000000001064961 in runtime.goexit 
  6.    at /usr/local/go/src/runtime/asm_amd64.s:1374 

以及 println 這一句的調(diào)用棧:

 
 
 
 
  1. 0  0x000000000106d1a0 in main.main.func1 
  2.    at ./a.go:7 
  3. 1  0x0000000001064961 in runtime.goexit 
  4.    at /usr/local/go/src/runtime/asm_amd64.s:1374 

可以看到,調(diào)用棧的最上層都是 runtime.goexit,我們跟著注明了的代碼行數(shù),順藤摸瓜,找到 goexit 代碼:

 
 
 
 
  1. // The top-most function running on a goroutine 
  2. // returns to goexit+PCQuantum. 
  3. TEXT runtime·goexit(SB),NOSPLIT,$0-0 
  4.     BYTE    $0x90   // NOP 
  5.     CALL    runtime·goexit1(SB) // does not return 
  6.     // traceback from goexit1 must hit code range of goexit 
  7.     BYTE    $0x90   // NOP 

這還是個(gè)匯編函數(shù),它接著調(diào)用 goexit1 函數(shù)、goexit0 函數(shù),主要的功能就是將 goroutine 的各個(gè)字段清零,放入 gFree 隊(duì)列里,等待將來(lái)進(jìn)行復(fù)用。

另一方面,goexit 函數(shù)的地址是在創(chuàng)建 goroutine 的過(guò)程中,塞到棧上的。讓 CPU “誤以為”:func() 是由 goexit 函數(shù)調(diào)用的。這樣一來(lái),當(dāng) func() 執(zhí)行完畢時(shí),會(huì)返回到 goexit 函數(shù)做一些清理工作。

下面這張圖能看出在 newg 的棧底塞了一個(gè) goexit 函數(shù)的地址:

goexit 返回地址

對(duì)應(yīng)的路徑是:

 
 
 
 
  1. newporc -> newporc1 -> gostartcallfn -> gostartcall 

來(lái)看 newproc1 中的關(guān)鍵幾行代碼:

 
 
 
 
  1. newg.sched.pc = funcPC(goexit) + sys.PCQuantum 
  2. newg.sched.g = guintptr(unsafe.Pointer(newg)) 
  3. gostartcallfn(&newg.sched, fn) 

這里的 newg 就是創(chuàng)建的 goroutine,每個(gè)新建的 goroutine 都會(huì)執(zhí)行這些代碼。而 sched 結(jié)構(gòu)體其實(shí)保存的是 goroutine 的執(zhí)行現(xiàn)場(chǎng),每當(dāng) goroutine 被調(diào)離 CPU,它的執(zhí)行進(jìn)度就是保存到這里。進(jìn)度主要就是 SP、BP、PC,分別表示棧頂?shù)刂?、棧底地址、指令位置,?goroutine 再次得到 CPU 的執(zhí)行權(quán)時(shí),會(huì)把 SP、BP、PC 加載到寄存器中,從而從斷點(diǎn)處恢復(fù)運(yùn)行。

回到上面的幾行代碼,pc 被賦值成了 funcPC(goexit),最后在 gostartcall 里:

 
 
 
 
  1. // adjust Gobuf as if it executed a call to fn with context ctxt 
  2. // and then did an immediate gosave. 
  3. func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) { 
  4.  sp := buf.sp 
  5.  ... 
  6.  sp -= sys.PtrSize 
  7.  *(*uintptr)(unsafe.Pointer(sp)) = buf.pc 
  8.  buf.sp = sp 
  9.  buf.pc = uintptr(fn) 
  10.  buf.ctxt = ctxt 

sp 其實(shí)就是棧頂,第 7 行代碼把 buf.pc,也就是 goexit 的地址,放在了棧頂?shù)牡胤?,熟?Go 函數(shù)調(diào)用規(guī)約的朋友知道,這個(gè)位置其實(shí)就是 return addr,將來(lái)等 func() 執(zhí)行完,就會(huì)回到父函數(shù)繼續(xù)執(zhí)行,這里的父函數(shù)其實(shí)就是 goexit。

一切早已注定。

不過(guò)注意一點(diǎn),main goroutine 和普通的 goroutine 不同的是,前者執(zhí)行完用戶 main 函數(shù)后,會(huì)直接執(zhí)行 exit 調(diào)用,整個(gè)進(jìn)程退出:

exit

也就不會(huì)進(jìn)入 goexit 函數(shù)。而普通 goroutine 執(zhí)行完畢后,則直接進(jìn)入 goexit 函數(shù),做一些清理工作。

這也就是為什么只要 main goroutine 執(zhí)行完了,就不會(huì)等其他 goroutine,直接退出。一切都是因?yàn)?exit 這個(gè)調(diào)用。

今天我們主要講了 goexit 是怎么被安插到 goroutine 的棧上,從而實(shí)現(xiàn) goroutine 執(zhí)行完畢后再回到 goexit 函數(shù)。

原來(lái)看似很不理解的東西,是不是更清晰了?

源碼面前,了無(wú)秘密。

好了,這就是今天全部的內(nèi)容了~ 我是小X,我們下期再見(jiàn)~


新聞標(biāo)題:曹大帶我學(xué)Go之哪里來(lái)的Goexit
網(wǎng)頁(yè)鏈接:http://www.5511xx.com/article/dhioips.html