成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

曹大帶我學 Go之哪里來的 Goexit

開發 后端
在學員群里,有同學在用 dlv 調試時看到了令人不解的 goexit:goexit 函數是啥,為啥 go fun(){}() 的上層是它?看著像是一個“退出”函數,為什么會出現在最上層?

[[404148]]

本文轉載自微信公眾號「碼農桃花源」,作者小X。轉載本文請聯系碼農桃花源公眾號。

你好,我是小X。

曹大最近開 Go 課程了,小X 正在和曹大學 Go。

這個系列會講一些從課程中學到的讓人醍醐灌頂的東西,撥云見日,帶你重新認識 Go。

在學員群里,有同學在用 dlv 調試時看到了令人不解的 goexit:goexit 函數是啥,為啥 go fun(){}() 的上層是它?看著像是一個“退出”函數,為什么會出現在最上層?

其實如果看過 pprof 的火焰圖,也會經常看到 goexit 這個函數。

我們來個例子重現一下:

  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

啟動 dlv 調試,并分別在不同的地方打上斷點:

  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 

執行命令 c 運行到斷點處,再執行 bt 命令得到 main 函數的調用棧:

  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 函數,它是 Go 進程的 main goroutine,這里會執行一些 init 操作、開啟 GC、執行用戶 main 函數……

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

其中 fn 是 main_main 函數,表示用戶的 main 函數,執行到了這里,才真正將權力交給用戶。

繼續執行 c 命令和 bt 命令,得到 go 這一行的調用棧:

  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 這一句的調用棧:

  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 

可以看到,調用棧的最上層都是 runtime.goexit,我們跟著注明了的代碼行數,順藤摸瓜,找到 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 

這還是個匯編函數,它接著調用 goexit1 函數、goexit0 函數,主要的功能就是將 goroutine 的各個字段清零,放入 gFree 隊列里,等待將來進行復用。

另一方面,goexit 函數的地址是在創建 goroutine 的過程中,塞到棧上的。讓 CPU “誤以為”:func() 是由 goexit 函數調用的。這樣一來,當 func() 執行完畢時,會返回到 goexit 函數做一些清理工作。

下面這張圖能看出在 newg 的棧底塞了一個 goexit 函數的地址:

goexit 返回地址

對應的路徑是:

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

來看 newproc1 中的關鍵幾行代碼:

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

這里的 newg 就是創建的 goroutine,每個新建的 goroutine 都會執行這些代碼。而 sched 結構體其實保存的是 goroutine 的執行現場,每當 goroutine 被調離 CPU,它的執行進度就是保存到這里。進度主要就是 SP、BP、PC,分別表示棧頂地址、棧底地址、指令位置,等 goroutine 再次得到 CPU 的執行權時,會把 SP、BP、PC 加載到寄存器中,從而從斷點處恢復運行。

回到上面的幾行代碼,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 其實就是棧頂,第 7 行代碼把 buf.pc,也就是 goexit 的地址,放在了棧頂的地方,熟悉 Go 函數調用規約的朋友知道,這個位置其實就是 return addr,將來等 func() 執行完,就會回到父函數繼續執行,這里的父函數其實就是 goexit。

一切早已注定。

不過注意一點,main goroutine 和普通的 goroutine 不同的是,前者執行完用戶 main 函數后,會直接執行 exit 調用,整個進程退出:

exit

也就不會進入 goexit 函數。而普通 goroutine 執行完畢后,則直接進入 goexit 函數,做一些清理工作。

這也就是為什么只要 main goroutine 執行完了,就不會等其他 goroutine,直接退出。一切都是因為 exit 這個調用。

今天我們主要講了 goexit 是怎么被安插到 goroutine 的棧上,從而實現 goroutine 執行完畢后再回到 goexit 函數。

原來看似很不理解的東西,是不是更清晰了?

源碼面前,了無秘密。

好了,這就是今天全部的內容了~ 我是小X,我們下期再見~

 

責任編輯:武曉燕 來源: 碼農桃花源
相關推薦

2021-06-10 09:00:32

Go底層代碼

2021-08-09 07:47:39

ExtraGoMap

2021-06-01 09:27:53

Ast Go語言

2021-07-15 08:58:15

指定配置項Go

2021-05-20 08:59:47

Go調度本質

2021-05-27 08:59:09

Go匯編命令

2022-01-05 08:56:20

Go火焰圖編程

2011-05-03 09:34:14

項目經理

2020-04-17 10:50:19

5G運營商網絡

2023-05-26 08:21:59

Lock_TimeMySQL

2021-12-31 14:39:29

AI 數據人工智能

2022-04-06 08:58:39

歸并排序Go算法

2018-05-23 10:23:18

數據系統機器學習

2015-05-04 17:36:49

大數據跨過幾道坎

2017-06-19 07:58:40

2021-10-10 15:01:09

Go 源碼Github

2022-02-09 07:52:36

GolangGo語言

2016-12-01 14:16:18

GitSCM配置

2020-07-31 07:55:21

JavaFuture接口

2021-02-22 09:30:09

go開發環境桌面系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日韩精品专区 | 亚洲精品一区二区三区蜜桃久 | 色婷婷综合久久久中文字幕 | 久久精品伊人 | 国产网站在线免费观看 | 亚洲久久久 | 亚洲视频在线看 | 日本在线观看视频 | 国产精品大全 | 91pron在线| 欧美一区二区免费视频 | 亚洲国产一区二区在线 | 男人天堂网址 | 福利片在线 | 黄色毛片在线观看 | 乱码av午夜噜噜噜噜动漫 | 激情欧美一区二区三区 | 日韩在线视频一区 | 国产精品久久久久久久三级 | 国产精品美女久久久久久免费 | 99精品免费久久久久久日本 | 日韩精品在线观看视频 | 九九热久久免费视频 | 黄色激情毛片 | 国产日韩久久 | 免费一级黄色录像 | 免费一级片 | av天天看| 亚洲一区二区三区在线播放 | 国产高清av免费观看 | 亚洲成人一区 | 精品无码久久久久久国产 | 日日摸夜夜爽人人添av | 91久久精品国产91久久 | 综合网中文字幕 | 日日操夜夜摸 | 99国产精品99久久久久久 | 欧美日韩综合精品 | 91视频91| 亚洲免费在线观看 | 久久毛片 |