<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 2.4 主 Goroutine 的生與死 上一節中我們已經知道`schedinit`完成初始化工作后并不會立即執行`runtime.main`(即主 Goroutine 運行的地方)。相反,會在后續的`mstart`調用中被調度器調度執行。 這個過程中,只會將`runtime.main`的入口地址壓棧,進而將其傳遞給`newproc`進行使用, 而后`newproc`完成 G 的創建保存到 G 的運行現場中,因此真正執行會等到`mstart`后才會被調度執行。 我們在調度器一章中詳細討論調度器的調度過程,現在我們先將目光聚焦在`runtime.main`已經開始執行時的情況。 ## 2.4.1 主 Goroutine 的一生 運行時包的 main 函數`runtime.main`承載了用戶代碼的 main 函數`main.main`, 并在同一個 Goroutine 上執行: ``` // 主 Goroutine func main() { ... // 執行棧最大限制:1GB(64位系統)或者 250MB(32位系統) if sys.PtrSize == 8 { maxstacksize = 1000000000 } else { maxstacksize = 250000000 } ... // 啟動系統后臺監控(定期垃圾回收、搶占調度等等) systemstack(func() { newm(sysmon, nil) }) ... // 執行 runtime.init。運行時包中有多個 init 函數,編譯器會將他們鏈接起來。 runtime_init() ... // 啟動垃圾回收器后臺操作 gcenable() ... // 執行用戶 main 包中的 init 函數,因為鏈接器設定運行時不知道 main 包的地址,處理為非間接調用 fn := main_init fn() ... // 執行用戶 main 包中的 main 函數,同理 fn = main_main fn() ... // 退出 exit(0) } ``` 整個執行過程有這樣幾個關鍵步驟: 1. `systemstack`會運行`newm(sysmon, nil)`啟動后臺監控 2. `runtime_init`負責執行運行時的多個初始化函數`runtime.init` 3. `gcenable`啟用垃圾回收器 4. `main_init`開始執行用戶態`main.init`函數,這意味著所有的`main.init`均在同一個主 Goroutine 中執行 5. `main_main`開始執行用戶態`main.main`函數,這意味著`main.main`和`main.init`均在同一個 Goroutine 中執行。 ## 2.4.2`pkg.init`的執行順序 運行時的`runtime_init`則由編譯器將多個`runtime.init`進行鏈接,我們可以從 函數的聲明中看到: ``` //go:linkname runtime_init runtime.init func runtime_init() ``` 運行時存在多個 init 函數,其中較為重要的幾個函數包括: 1. 垃圾回收器所需的參數檢查并創建強制啟動 GC 的監控 Goroutine ``` const ( _WorkbufSize = 2048 workbufAlloc = 32 &lt;&lt; 10 ) func init() { if workbufAlloc%pageSize != 0 || workbufAlloc%_WorkbufSize != 0 { throw("bad workbufAlloc") } } func init() { go forcegchelper() } ``` 2. 確定`defer`的運行時類型: ``` var deferType *_type // _defer 結構的類型 func init() { var x interface{} x = (*_defer)(nil) deferType = (*(**ptrtype)(unsafe.Pointer(&amp;x))).elem } ``` 從這兩個`init`函數可以看出,在用戶代碼正式啟動之前,運行時還額外準備了強制 GC 的 監控并確定了 defer 的類型。 本節中我們不對這些方法做詳細分析,等到他們各自的章節中再做詳談。那么我們仍然還會有這樣 的疑問:包含多個`init`的執行順序怎樣由編譯器控制的? 我們可以驗證下面這兩個不同的程序: ``` // main1.go package main import ( "fmt" _ "net/http" ) func main() { fmt.Printf("hello, %s", "world!") } ``` ``` // main2.go package main import ( _ "net/http" "fmt" ) func main() { fmt.Printf("hello, %s", "world!") } ``` 他們的唯一區別就是導入包的順序不同,通過`go tool objdump -s "main.init"`可以獲得`init`函數的實際匯編代碼: ~~~asm TEXT main.init.0(SB) main1.go:8 0x11f0f40 65488b0c2530000000 MOVQ GS:0x30, CX ... main1.go:9 0x11f0f76 e8a5b8e3ff CALL runtime.printstring(SB) ... TEXT main.init(SB) <autogenerated> ... <autogenerated>:1 0x11f10a8 e8e3b0ebff CALL fmt.init(SB) <autogenerated>:1 0x11f10ad e88e5affff CALL net/http.init(SB) <autogenerated>:1 0x11f10b2 e889feffff CALL main.init.0(SB) ... ~~~ ~~~asm TEXT main.init.0(SB) ... main2.go:10 0x11f0f76 e8a5b8e3ff CALL runtime.printstring(SB) ... TEXT main.init(SB) <autogenerated> <autogenerated>:1 0x11f1060 65488b0c2530000000 MOVQ GS:0x30, CX ... <autogenerated>:1 0x11f10a8 e8935affff CALL net/http.init(SB) <autogenerated>:1 0x11f10ad e81e40ecff CALL fmt.init(SB) <autogenerated>:1 0x11f10b2 e889feffff CALL main.init.0(SB) ... ~~~ 從實際的匯編代碼可以看到,init 的順序由實際包調用順序給出,所有引入的外部包的 init 均會被 編譯器安插在當前包的`main.init.0`之前執行,而外部包的順序與引入包的順序有關。 那么某個包內的多個 init 函數是否有順序可言?我們簡單看一看編譯器關于 init 函數的實現: ``` // cmd/compile/internal/gc/init.go // 將 init 的名字 pkg.init 重命名為 pkg.init.0 var renameinitgen int func renameinit() *types.Sym { s := lookupN("init.", renameinitgen) renameinitgen++ return s } ``` `renameinit`這個函數中實現了對 init 函數的重命名,并通過`renameinitgen`在全局記錄了 init 的索引后綴。`renameinit`會在處理函數聲明時被調用: ``` // cmd/compile/internal/gc/noder.go func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { name := p.name(fun.Name) t := p.signature(fun.Recv, fun.Type) f := p.nod(fun, ODCLFUNC, nil, nil) // 函數沒有 reciver if fun.Recv == nil { // 且名字叫做 init if name.Name == "init" { name = renameinit() // 對其進行重命名 ... } ... } ... } ``` 而`funcDecl`則會在 AST 的`noder`結構的方法`decls`中被調用: ``` func (p *noder) decls(decls []syntax.Decl) (l []*Node) { var cs constState for _, decl := range decls { p.lineno(decl) switch decl := decl.(type) { case *syntax.FuncDecl: l = append(l, p.funcDecl(decl)) ... } } return } ``` 一個包內的 init 函數的調用順序取決于聲明的順序,即從上而下依次調用。 ## 2.4.3 小結 看到這里我們已經結束了整個 Go 程序的執行,但仍有海量的細節還沒有被敲定,完全還沒有深入 運行時的三大核心組件,運行時各類機制也都還沒有接觸。總結一下這節討論中遺留下來的問題: 1. `mstart`會如何將主 Goroutine 調度執行? 2. `sysmon`系統監控做了什么事情,它的工作原理是什么? 3. `runtime.init`的`forcegchelper`是什么?`gcenable`又做了什么? 我們在隨后的章節中一一介紹。 ## 進一步閱讀的參考文獻 1. [Command compile](https://golang.org/cmd/compile/) 2. [`main_init_done`can be implemented more efficiently](https://github.com/golang/go/issues/15943)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看