91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang并發編程之調度器初始化的方法是什么

發布時間:2023-05-11 17:21:42 來源:億速云 閱讀:295 作者:iii 欄目:開發技術

本篇內容主要講解“Golang并發編程之調度器初始化的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Golang并發編程之調度器初始化的方法是什么”吧!

1. 一些全局變量

proc.goruntime.go中有一些很重要的全局的變量,我們將其先列出來:

var (
   m0 m // 代表第一個起來的線程,即主線程
   g0 g // m0的g0,即 m0.g0 = &g0
   
   allgs   []*g    // 保存所有的g
   allglen uintptr // 所有g的長度
   
   
   allm       *m     // 保存所有的m
   gomaxprocs int32  // p的最大個數,默認等于 ncpu
   ncpu       int32  // 程序啟動時,會調用osinit函數獲得此值
   sched      schedt // 調度器的結構體對象,全局僅此一份
)

程序初始化時,這些全局變量最開始都會被初始化為空值,然后隨著一些初始化函數的作用,這些變量才會開始被賦值。

2. main函數之前

package main

import "fmt"

func main() {
	fmt.Println("hello world")
}

在項目根目錄下執行go build -gcflags "-N -l" -o main main.go-gcflags "-N -l"是為了關閉編譯器的優化和函數內聯。然后我們使用gdb調試代碼:

$ gdb main
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
...
(gdb) info files
Symbols from "/home/chenyiguo/smb_share/go_routine_test/main".
Local exec file:
    `/home/chenyiguo/smb_share/go_routine_test/main', file type elf64-x86-64.
    Entry point: 0x45c220
    0x0000000000401000 - 0x000000000047e357 is .text
    0x000000000047f000 - 0x00000000004b3ecc is .rodata
    0x00000000004b4060 - 0x00000000004b4538 is .typelink
    0x00000000004b4540 - 0x00000000004b4598 is .itablink
    0x00000000004b4598 - 0x00000000004b4598 is .gosymtab
    0x00000000004b45a0 - 0x000000000050ce10 is .gopclntab
    0x000000000050d000 - 0x000000000050d020 is .go.buildinfo
    0x000000000050d020 - 0x000000000051d600 is .noptrdata
    0x000000000051d600 - 0x0000000000524e10 is .data
    0x0000000000524e20 - 0x0000000000553d28 is .bss
    0x0000000000553d40 - 0x00000000005590a0 is .noptrbss
    0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid

可以看到,程序入口地址是0x45c220,繼續打斷點b *0x45c220進入,可以看到,程序代碼的入口就在/usr/local/go/src/runtime/rt0_linux_amd64.s的第8行。

(gdb) b *0x45c220
Breakpoint 1 at 0x45c220: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.

進入代碼位置,可以看到,其第8行是調到_rt0_amd64(SB)函數運行。

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
   JMP    _rt0_amd64(SB)

再全局搜索_rt0_amd64,可以發現,在asm_amd64.s中有如下代碼,最終會執行到runtime·rt0_go(SB)代碼,在asm_amd64.s中,我們可以找到runtime·rt0_go代碼的實現,這也是匯編語言。

TEXT _rt0_amd64(SB),NOSPLIT,$-8
   MOVQ   0(SP), DI  // argc
   LEAQ   8(SP), SI  // argv
   JMP    runtime·rt0_go(SB)

rt0_go函數會完成Go程序啟動的所有初始化工作,這個函數比較長,也比較復雜,我們可以分段來看:

TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
   // copy arguments forward on an even stack
   MOVQ   DI, AX    // argc
   MOVQ   SI, BX    // argv
   SUBQ   $(5*8), SP    // 3args 2auto
   ANDQ   $~15, SP
   MOVQ   AX, 24(SP)
   MOVQ   BX, 32(SP)

以上一段我們不用深究,第四條指令調整棧(內核主線程棧)頂指針16字節對齊,然后存儲了argcargv數組地址。

2.1 初始化g0

注意,此處提及的g0是全局變量g0,即主線程m0m0.g0

// create istack out of the given (operating system) stack.
// _cgo_init may update stackguard.
MOVQ   $runtime·g0(SB), DI        // g0的地址存放在DI寄存器
LEAQ   (-64*1024+104)(SP), BX     // BX=SP-64*1024+104
MOVQ   BX, g_stackguard0(DI)      // g0.stackguard0=SP-64*1024+104
MOVQ   BX, g_stackguard1(DI)      // g0.stackguard1=SP-64*1024+104
MOVQ   BX, (g_stack+stack_lo)(DI) // g0.stack.lo=SP-64*1024+104
MOVQ   SP, (g_stack+stack_hi)(DI) // g0.stack.hi=SP

從以上代碼可以看出,系統為主線程m0g0在系統線程的棧空間開辟了一個大約有64KB大小的棧,地址范圍是SP-64*1024+104 ~ SP。完成以上指令后,系統棧與g0的關系大致如圖所示:

Golang并發編程之調度器初始化的方法是什么

2.2 主線程與m0的綁定

LEAQ    runtime·m0+m_tls(SB), DI // DI=&m0.tls
CALL   runtime·settls(SB)        // 調用settls函數設置本地線程存儲

// store through it, to make sure it works
get_tls(BX)
MOVQ   $0x123, g(BX)
MOVQ   runtime·m0+m_tls(SB), AX
CMPQ   AX, $0x123
JEQ 2(PC)
CALL   runtime·abort(SB)

前面兩段代碼,前兩條指令通過runtime·settls來設置本地線程存儲,后面一段是驗證設置是否成功。下面我們看下runtime·settls到底做了什么。

// set tls base to DI
TEXT runtime·settls(SB),NOSPLIT,$32
#ifdef GOOS_android
   // Android stores the TLS offset in runtime·tls_g.
   SUBQ   runtime·tls_g(SB), DI                // 不會走到這里,這是Android系統的
#else
   ADDQ   $8, DI // ELF wants to use -8(FS)    // 這之后,DI存放的就是m0.tls[1]的地址了
#endif
   MOVQ   DI, SI                               // 將DI值賦給SI,即m0.tls[1]的地址,作為系統調用的第二個參數
   MOVQ   $0x1002, DI    // ARCH_SET_FS        // DI是第一個參數,0x1002表示操作ARCH_SET_FS,這是個int類型的code,表示設置FS段寄存器為SI寄存器的值,即m0.tls[1]
   MOVQ   $SYS_arch_prctl, AX                  // 接下來就是系統調用了
   SYSCALL
   CMPQ   AX, $0xfffffffffffff001
   JLS    2(PC)
   MOVL   $0xf1, 0xf1  // crash
   RET

上面代碼表明,通過arch_prctl的系統調用,將FS段寄存器的值設置為了m0.tls[1]的地址。操作系統在把線程調離CPU運行時會幫我們把所有寄存器中的值保存在內存中,調度線程起來運行時又會從內存中把這些寄存器的值恢復到CPU,這樣,在此之后,工作線程代碼就可以通過FS寄存器來找到m.tls。從而,就實現了主線程與m0之間的綁定。

為了讀懂以上代碼,我們需要知道的是,get_tlsg是宏實現,在runtime/go_tls.h中,如下。所以我們知道,get_tls(r)會將m0.tls的地址賦給r;而看了后面的操作,你就會明白,g(r)則會取出對應的g地址。

#ifdef GOARCH_amd64
#define    get_tls(r) MOVQ TLS, r
#define    g(r)   0(r)(TLS*1)
#endif

2.3 m0和g0的綁定

ok:
   // set the per-goroutine and per-mach "registers"
   get_tls(BX)
   LEAQ   runtime·g0(SB), CX // CX=&g0
   MOVQ   CX, g(BX)          // m0.tls[0]=&g0
   LEAQ   runtime·m0(SB), AX // AX=&m0

   // save m->g0 = g0
   MOVQ   CX, m_g0(AX)       // m0.g0=&g0
   // save m0 to g0->m       
   MOVQ   AX, g_m(CX)        // g0.m = m0

就這樣,將g0m0進行了深刻地綁定

Golang并發編程之調度器初始化的方法是什么

2.4 調度器的初始化

在接下來的代碼中又是一些需求項的檢查,我們直接忽略,看以下代碼:

MOVL    24(SP), AX    // copy argc  // AX=argc
MOVL   AX, 0(SP)                    // argc放到棧頂
MOVQ   32(SP), AX    // copy argv   // AX=argv
MOVQ   AX, 8(SP)                    // argv放到SP+8的位置
CALL   runtime·args(SB)             // 處理操作系統傳過來的參數和env,無需關心
CALL   runtime·osinit(SB)           // linux系統的osinit沒有做很多事,只是賦值了ncpu和物理頁大小
CALL   runtime·schedinit(SB)        // 調度器的初始化

調度器的初始化是在runtime.schedinit函數中完成的,是用go代碼寫的。

// The bootstrap sequence is:
//
// call osinit
// call schedinit
// make & queue new G
// call runtime·mstart
//
// The new G calls runtime·main.
func schedinit() {
   // a lot of lock init
   ...

   // raceinit must be the first call to race detector.
   // In particular, it must be done before mallocinit below calls racemapshadow.
   // getg函數源碼沒有定義,在編譯的時候由編譯器插入,類似下面的代碼
   // get_tls(CX)
   // MOVQ g(CX), BX
   _g_ := getg()  // 獲取的 _g_ = &g0
   if raceenabled {
      _g_.racectx, raceprocctx0 = raceinit()
   }

   sched.maxmcount = 10000  // 操作系統線程個數最多為10000

   // a lot of init
   ...
   
   // 初始化m0
   mcommoninit(_g_.m, -1)
   
   // 一些其他設置,暫時忽略
   ...
   
   sched.lastpoll = uint64(nanotime())
   // p的數目確定
   procs := ncpu
   if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
      procs = n
   }
   // 初始化p
   if procresize(procs) != nil {
      throw("unknown runnable goroutine during bootstrap")
   }
   
   ...
}

從上面我們可以看出,雖然在匯編代碼中將m0g0進行了一些數據的綁定,但是并沒有真正初始化m0。所以在schedinit函數中,我們有兩個重要的工作要做:

  • 通過函數mcommoninit初始化m0

  • 通過函數procresize初始化p,初始化出來的p的數目一般而言是系統的CPU核數(ncpu),除非用戶設置了GOMAXPROCS

2.4.1 初始化m0

// Pre-allocated ID may be passed as 'id', or omitted by passing -1.
func mcommoninit(mp *m, id int64) {
   _g_ := getg() // _g_ = &g0

   // g0 stack won't make sense for user (and is not necessary unwindable).
   if _g_ != _g_.m.g0 {
      callers(1, mp.createstack[:])
   }

   lock(&sched.lock)

   if id >= 0 {
      mp.id = id
   } else {
      mp.id = mReserveID() // 初次從mReserveID()獲取到的id=0
   }

   // random初始化,用于竊取 G
   lo := uint32(int64Hash(uint64(mp.id), fastrandseed))
   hi := uint32(int64Hash(uint64(cputicks()), ^fastrandseed))
   if lo|hi == 0 {
      hi = 1
   }
   // Same behavior as for 1.17.
   // TODO: Simplify ths.
   if goarch.BigEndian {
      mp.fastrand = uint64(lo)<<32 | uint64(hi)
   } else {
      mp.fastrand = uint64(hi)<<32 | uint64(lo)
   }

   // 創建用于信號處理的gsignal,只是簡單的從堆上分配一個g結構體對象,然后把棧設置好就返回了
   mpreinit(mp)
   if mp.gsignal != nil {
      mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard
   }

   // 把m0掛入全局鏈表allm中
   // Add to allm so garbage collector doesn't free g->m
   // when it is just in a register or thread-local storage.
   mp.alllink = allm

   // NumCgoCall() iterates over allm w/o schedlock,
   // so we need to publish it safely.
   atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp))
   unlock(&sched.lock)

   // Allocate memory to hold a cgo traceback if the cgo call crashes.
   if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" {
      mp.cgoCallers = new(cgoCallers)
   }
}

從函數可以看出,這里并未對傳入的m做有關調度的初始化,可以簡單認為這個函數只是把m0放到了全局鏈表allm中后就返回了。

2.4.2 初始化allp

func procresize(nprocs int32) *p {
   ...

   old := gomaxprocs // 系統初始化的時候, gomaxprocs=0
   if old < 0 || nprocs <= 0 {
      throw("procresize: invalid arg")
   }
   
   ...

   // 看看是否需要擴大allp,初始化時len(allp)=0,所以肯定會增長
   // Grow allp if necessary.
   if nprocs > int32(len(allp)) {
      // Synchronize with retake, which could be running
      // concurrently since it doesn't run on a P.
      lock(&allpLock)
      if nprocs <= int32(cap(allp)) {
         allp = allp[:nprocs]
      } else {
         nallp := make([]*p, nprocs)
         // Copy everything up to allp's cap so we
         // never lose old allocated Ps.
         copy(nallp, allp[:cap(allp)])
         allp = nallp
      }

      if maskWords <= int32(cap(idlepMask)) {
         idlepMask = idlepMask[:maskWords]
         timerpMask = timerpMask[:maskWords]
      } else {
         nidlepMask := make([]uint32, maskWords)
         // No need to copy beyond len, old Ps are irrelevant.
         copy(nidlepMask, idlepMask)
         idlepMask = nidlepMask

         ntimerpMask := make([]uint32, maskWords)
         copy(ntimerpMask, timerpMask)
         timerpMask = ntimerpMask
      }
      unlock(&allpLock)
   }

   // 初始化這些P
   // initialize new P's
   for i := old; i < nprocs; i++ {
      pp := allp[i]
      if pp == nil {
         pp = new(p)
      }
      pp.init(i)
      atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp))
   }

   _g_ := getg() // _g_ = g0
   if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs { // 初始化時m0.p=0,所以不會進這個分支
      // continue to use the current P
      _g_.m.p.ptr().status = _Prunning
      _g_.m.p.ptr().mcache.prepareForSweep()
   } else {
      // release the current P and acquire allp[0].
      //
      // We must do this before destroying our current P
      // because p.destroy itself has write barriers, so we
      // need to do that from a valid P.
      if _g_.m.p != 0 {
         if trace.enabled {
            // Pretend that we were descheduled
            // and then scheduled again to keep
            // the trace sane.
            traceGoSched()
            traceProcStop(_g_.m.p.ptr())
         }
         _g_.m.p.ptr().m = 0
      }
      _g_.m.p = 0
      p := allp[0]
      p.m = 0
      p.status = _Pidle
      acquirep(p) // 把p和m0關聯起來         
      if trace.enabled {
         traceGoStart()
      }
   }

   // g.m.p is now set, so we no longer need mcache0 for bootstrapping.
   mcache0 = nil

   // release resources from unused P's
   for i := nprocs; i < old; i++ {
      p := allp[i]
      p.destroy()
      // can't free P itself because it can be referenced by an M in syscall
   }

   // Trim allp.
   if int32(len(allp)) != nprocs {
      lock(&allpLock)
      allp = allp[:nprocs]
      idlepMask = idlepMask[:maskWords]
      timerpMask = timerpMask[:maskWords]
      unlock(&allpLock)
   }

   // 將所有的空閑的p放入空閑鏈表
   var runnablePs *p
   for i := nprocs - 1; i >= 0; i-- {
      p := allp[i]
      if _g_.m.p.ptr() == p {
         continue
      }
      p.status = _Pidle
      if runqempty(p) {
         pidleput(p)
      } else {
         p.m.set(mget())
         p.link.set(runnablePs)
         runnablePs = p
      }
   }
   stealOrder.reset(uint32(nprocs))
   var int32p *int32 = &gomaxprocs // make compiler check that gomaxprocs is an int32
   atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs))
   return runnablePs
}

其實,以上代碼可以總結如下:

  • 使用make([]*p, nprocs)初始化全局變量allp,即allp = make([]*p, nprocs)

  • 循環創建并初始化nprocs個p結構體對象并依次保存在allp切片之中

  • m0allp[0]綁定在一起,即m0.p = allp[0], allp[0].m = m0

  • 把除了allp[0]之外的所有p放入到全局變量schedpidle空閑隊列之中

至此,整個調度器中各組件之間的關系如下圖所示:

Golang并發編程之調度器初始化的方法是什么

到此,相信大家對“Golang并發編程之調度器初始化的方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

炎陵县| 杭州市| 泸州市| 晴隆县| 鸡西市| 军事| 墨竹工卡县| 怀柔区| 怀仁县| 屯门区| 沧源| 南充市| 微山县| 鲁山县| 安图县| 小金县| 于都县| 西宁市| 扬中市| 积石山| 石门县| 林西县| 松溪县| 潍坊市| 汶上县| 澜沧| 独山县| 满城县| 涿州市| 册亨县| 蓝田县| 河东区| 辛集市| 来宾市| 南丰县| 台东市| 普安县| 大安市| 锦州市| 柞水县| 富阳市|