最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用詳解

 更新時(shí)間:2023年04月06日 16:01:09   作者:IguoChan  
這篇文章腫,將以一個(gè)簡(jiǎn)單的文件打開(kāi)的系統(tǒng)調(diào)用,來(lái)分析一下Go調(diào)度器在系統(tǒng)調(diào)用時(shí)做了什么。文中的示例代碼講解詳細(xì),需要的可以參考一下

0. 簡(jiǎn)介

上篇博客,我們分析了Go調(diào)度器中的搶占策略,這篇,我們將分析一下,在系統(tǒng)調(diào)用時(shí)發(fā)生的調(diào)度行為。

1. 系統(tǒng)調(diào)用

下面,我們將以一個(gè)簡(jiǎn)單的文件打開(kāi)的系統(tǒng)調(diào)用,來(lái)分析一下Go調(diào)度器在系統(tǒng)調(diào)用時(shí)做了什么。

1.1 場(chǎng)景

package main

import (
   "fmt"
   "io/ioutil"
   "os"
)

func main() {
   f, err := os.Open("./file")
   if err != nil {
      panic(err)
   }
   defer f.Close()

   content, err := ioutil.ReadAll(f)
   if err != nil {
      panic(err)
   }
   fmt.Println(string(content))
}

如上簡(jiǎn)單的代碼,讀取一個(gè)名為file的本地文件,然后打印其數(shù)據(jù),我們通過(guò)匯編代碼來(lái)分析一下其調(diào)用過(guò)程:

$ go build -gcflags "-N -l" -o main main.go
$ objdump -d main >> main.i

可以發(fā)現(xiàn),在main.i中,從main.main函數(shù),對(duì)于文件Open操作的調(diào)用關(guān)系為:main.main -> os.Open -> os.openFile -> os.openFileNolog -> syscall.openat -> syscall.Syscall6.abi0 -> runtime.entersyscall.abi0,而Syscall6的匯編如下:

TEXT ·Syscall6(SB),NOSPLIT,$0-80
   CALL   runtime·entersyscall(SB)
   MOVQ   a1+8(FP), DI
   MOVQ   a2+16(FP), SI
   MOVQ   a3+24(FP), DX
   MOVQ   a4+32(FP), R10
   MOVQ   a5+40(FP), R8
   MOVQ   a6+48(FP), R9
   MOVQ   trap+0(FP), AX // syscall entry
   SYSCALL
   CMPQ   AX, $0xfffffffffffff001
   JLS    ok6
   MOVQ   $-1, r1+56(FP)
   MOVQ   $0, r2+64(FP)
   NEGQ   AX
   MOVQ   AX, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET
ok6:
   MOVQ   AX, r1+56(FP)
   MOVQ   DX, r2+64(FP)
   MOVQ   $0, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET

1.2 陷入系統(tǒng)調(diào)用

可以發(fā)現(xiàn),系統(tǒng)調(diào)用最終會(huì)進(jìn)入到runtime.entersyscall函數(shù):

func entersyscall() {
   reentersyscall(getcallerpc(), getcallersp())
}

runtime.entersyscall函數(shù)會(huì)調(diào)用runtime.reentersyscall

func reentersyscall(pc, sp uintptr) {
   _g_ := getg()

   // Disable preemption because during this function g is in Gsyscall status,
   // but can have inconsistent g->sched, do not let GC observe it.
   _g_.m.locks++

   // Entersyscall must not call any function that might split/grow the stack.
   // (See details in comment above.)
   // Catch calls that might, by replacing the stack guard with something that
   // will trip any stack check and leaving a flag to tell newstack to die.
   _g_.stackguard0 = stackPreempt
   _g_.throwsplit = true

   // Leave SP around for GC and traceback.
   save(pc, sp)  // 保存pc和sp
   _g_.syscallsp = sp
   _g_.syscallpc = pc
   casgstatus(_g_, _Grunning, _Gsyscall)
   if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
      systemstack(func() {
         print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
         throw("entersyscall")
      })
   }

   if trace.enabled {
      systemstack(traceGoSysCall)
      // systemstack itself clobbers g.sched.{pc,sp} and we might
      // need them later when the G is genuinely blocked in a
      // syscall
      save(pc, sp)
   }

   if atomic.Load(&sched.sysmonwait) != 0 {
      systemstack(entersyscall_sysmon)
      save(pc, sp)
   }

   if _g_.m.p.ptr().runSafePointFn != 0 {
      // runSafePointFn may stack split if run on this stack
      systemstack(runSafePointFn)
      save(pc, sp)
   }

   // 一下解綁P和M
   _g_.m.syscalltick = _g_.m.p.ptr().syscalltick
   _g_.sysblocktraced = true
   pp := _g_.m.p.ptr()
   pp.m = 0
   _g_.m.oldp.set(pp)  // 存儲(chǔ)一下舊P
   _g_.m.p = 0
   atomic.Store(&pp.status, _Psyscall)
   if sched.gcwaiting != 0 {
      systemstack(entersyscall_gcwait)
      save(pc, sp)
   }

   _g_.m.locks--
}

可以發(fā)現(xiàn),runtime.reentersyscall除了做一些保障性的工作外,最重要的是做了以下三件事:

  • 保存當(dāng)前goroutine的PC和棧指針SP的內(nèi)容;
  • 將當(dāng)前goroutine的狀態(tài)置為_Gsyscall;
  • 將當(dāng)前P的狀態(tài)置為_Psyscall,并解綁P和M,讓當(dāng)前M陷入內(nèi)核的系統(tǒng)調(diào)用中,P被釋放,可以被其他找工作的M找到并且執(zhí)行剩下的goroutine。

1.3 從系統(tǒng)調(diào)用恢復(fù)

func exitsyscall() {
   _g_ := getg()

   _g_.m.locks++ // see comment in entersyscall
   if getcallersp() > _g_.syscallsp {
      throw("exitsyscall: syscall frame is no longer valid")
   }

   _g_.waitsince = 0
   oldp := _g_.m.oldp.ptr()  // 拿到開(kāi)始存儲(chǔ)的舊P
   _g_.m.oldp = 0
   if exitsyscallfast(oldp) {
      if trace.enabled {
         if oldp != _g_.m.p.ptr() || _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
            systemstack(traceGoStart)
         }
      }
      // There's a cpu for us, so we can run.
      _g_.m.p.ptr().syscalltick++
      // We need to cas the status and scan before resuming...
      casgstatus(_g_, _Gsyscall, _Grunning)

      ...

      return
   }

   ...

   // Call the scheduler.
   mcall(exitsyscall0)

   // Scheduler returned, so we're allowed to run now.
   // Delete the syscallsp information that we left for
   // the garbage collector during the system call.
   // Must wait until now because until gosched returns
   // we don't know for sure that the garbage collector
   // is not running.
   _g_.syscallsp = 0
   _g_.m.p.ptr().syscalltick++
   _g_.throwsplit = false
}

其中,exitsyscallfast函數(shù)有以下個(gè)分支:

  • 如果舊的P還沒(méi)有被其他M占用,依舊處于_Psyscall狀態(tài),那么直接通過(guò)wirep函數(shù)獲取這個(gè)P,返回true;
  • 如果舊的P被占用了,那么調(diào)用exitsyscallfast_pidle去獲取空閑的P來(lái)執(zhí)行,返回true;
  • 如果沒(méi)有空閑的P,則返回false;
//go:nosplit
func exitsyscallfast(oldp *p) bool {
   _g_ := getg()

   // Freezetheworld sets stopwait but does not retake P's.
   if sched.stopwait == freezeStopWait {
      return false
   }

   // 如果上一個(gè)P沒(méi)有被其他M占用,還處于_Psyscall狀態(tài),那么直接通過(guò)wirep函數(shù)獲取此P
   // Try to re-acquire the last P.
   if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
      // There's a cpu for us, so we can run.
      wirep(oldp)
      exitsyscallfast_reacquired()
      return true
   }

   // Try to get any other idle P.
   if sched.pidle != 0 {
      var ok bool
      systemstack(func() {
         ok = exitsyscallfast_pidle()
         if ok && trace.enabled {
            if oldp != nil {
               // Wait till traceGoSysBlock event is emitted.
               // This ensures consistency of the trace (the goroutine is started after it is blocked).
               for oldp.syscalltick == _g_.m.syscalltick {
                  osyield()
               }
            }
            traceGoSysExit(0)
         }
      })
      if ok {
         return true
      }
   }
   return false
}

當(dāng)exitsyscallfast函數(shù)返回false后,則會(huì)調(diào)用exitsyscall0函數(shù)去處理:

func exitsyscall0(gp *g) {
   casgstatus(gp, _Gsyscall, _Grunnable)
   dropg() // 因?yàn)楫?dāng)前m沒(méi)有找到p,所以先解開(kāi)g和m
   lock(&sched.lock)
   var _p_ *p
   if schedEnabled(gp) {
      _p_ = pidleget() // 還是嘗試找一下有沒(méi)有空閑的p
   }
   var locked bool
   if _p_ == nil { // 如果還是沒(méi)有空閑p,那么把g扔到全局隊(duì)列去等待調(diào)度
      globrunqput(gp)

      // Below, we stoplockedm if gp is locked. globrunqput releases
      // ownership of gp, so we must check if gp is locked prior to
      // committing the release by unlocking sched.lock, otherwise we
      // could race with another M transitioning gp from unlocked to
      // locked.
      locked = gp.lockedm != 0
   } else if atomic.Load(&sched.sysmonwait) != 0 {
      atomic.Store(&sched.sysmonwait, 0)
      notewakeup(&sched.sysmonnote)
   }
   unlock(&sched.lock)
   if _p_ != nil { // 如果找到了空閑p,那么就去執(zhí)行,這個(gè)分支永遠(yuǎn)不會(huì)返回
      acquirep(_p_)
      execute(gp, false) // Never returns.
   }
   if locked {
      // Wait until another thread schedules gp and so m again.
      //
      // N.B. lockedm must be this M, as this g was running on this M
      // before entersyscall.
      stoplockedm()
      execute(gp, false) // Never returns.
   }
   stopm() // 這里還是沒(méi)有找到空閑p的條件,停止這個(gè)m,因?yàn)闆](méi)有p,所以m應(yīng)該要開(kāi)始找工作了
   schedule() // Never returns. // 通過(guò)schedule函數(shù)進(jìn)行調(diào)度
}

exitsyscall0函數(shù)還是會(huì)嘗試找一個(gè)空閑的P,沒(méi)有的話就把goroutine扔到全局隊(duì)列,然后停止這個(gè)M,并且調(diào)用schedule函數(shù)等待調(diào)度;如果找到了空閑P,則會(huì)利用這個(gè)P去執(zhí)行此goroutine。

2. 小結(jié)

通過(guò)以上分析,可以發(fā)現(xiàn)goroutine有關(guān)系統(tǒng)調(diào)用的調(diào)度還是比較簡(jiǎn)單的:

  • 在發(fā)生系統(tǒng)調(diào)用時(shí)會(huì)將此goroutine設(shè)置為_Gsyscall狀態(tài);
  • 并將P設(shè)置為_Psyscall狀態(tài),并且解綁M和P,使得這個(gè)P可以去執(zhí)行其他的goroutine,而M就陷入系統(tǒng)內(nèi)核調(diào)用中了;
  • 當(dāng)該M從內(nèi)核調(diào)用中恢復(fù)到用戶態(tài)時(shí),會(huì)優(yōu)先去獲取原來(lái)的舊P,如果該舊P還未被其他M占用,則利用該P(yáng)繼續(xù)執(zhí)行本goroutine;
  • 如果沒(méi)有獲取到舊P,那么會(huì)嘗試去P的空閑列表獲取一個(gè)P來(lái)執(zhí)行;
  • 如果空閑列表中沒(méi)有獲取到P,就會(huì)把goroutine扔到全局隊(duì)列中,等到繼續(xù)執(zhí)行。

可以發(fā)現(xiàn),如果系統(tǒng)發(fā)生著很頻繁的系統(tǒng)調(diào)用,很可能會(huì)產(chǎn)生很多的M,在IO密集型的場(chǎng)景下,甚至?xí)l(fā)生線程數(shù)超過(guò)10000的panic事件。而Go團(tuán)隊(duì)為此也進(jìn)行了很多努力,下一節(jié)我們將介紹的網(wǎng)絡(luò)輪詢器將介紹,至少在網(wǎng)絡(luò)IO密集型場(chǎng)景,Go SDK是怎么優(yōu)化的。

以上就是Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用詳解的詳細(xì)內(nèi)容,更多關(guān)于Go調(diào)度器 系統(tǒng)調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go?Gin框架路由相關(guān)bug分析

    Go?Gin框架路由相關(guān)bug分析

    這篇文章主要為大家介紹了Go?Gin框架路由相關(guān)bug分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go1.21新增slices包中函數(shù)的用法詳解

    Go1.21新增slices包中函數(shù)的用法詳解

    Go?1.21新增的?slices?包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文為大家整理了部分函數(shù)的具體用法,感興趣的小伙伴可以了解一下
    2023-08-08
  • go語(yǔ)言學(xué)習(xí)之包和變量詳解

    go語(yǔ)言學(xué)習(xí)之包和變量詳解

    這篇文章主要給大家愛(ài)介紹了關(guān)于go語(yǔ)言學(xué)習(xí)之包和變量的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用go語(yǔ)言具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • Go語(yǔ)言map用法實(shí)例分析

    Go語(yǔ)言map用法實(shí)例分析

    這篇文章主要介紹了Go語(yǔ)言map用法,實(shí)例分析了map的功能及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • golang讀取yaml配置文件的示例代碼

    golang讀取yaml配置文件的示例代碼

    在項(xiàng)目開(kāi)發(fā)中,經(jīng)常需要把一些配置文件常量提取到統(tǒng)一配置文件進(jìn)行維護(hù),go項(xiàng)目在開(kāi)發(fā)中常常把需要維護(hù)的常量或者配置提取到y(tǒng)aml文件,所以本文主要來(lái)為大家介紹一下golang如何讀取yaml配置文件吧
    2023-11-11
  • Go實(shí)現(xiàn)mongodb增刪改查工具類的代碼示例

    Go實(shí)現(xiàn)mongodb增刪改查工具類的代碼示例

    這篇文章主要給大家介紹了關(guān)于Go實(shí)現(xiàn)mongodb增刪改查工具類的相關(guān)資料,MongoDB是一個(gè)NoSQL數(shù)據(jù)庫(kù),它提供了靈活的文檔存儲(chǔ)模型以及強(qiáng)大的查詢和操作功能,需要的朋友可以參考下
    2023-10-10
  • Go語(yǔ)言調(diào)用ffmpeg-api實(shí)現(xiàn)音頻重采樣

    Go語(yǔ)言調(diào)用ffmpeg-api實(shí)現(xiàn)音頻重采樣

    最近對(duì)golang處理音視頻很感興趣,對(duì)golang音視頻常用庫(kù)goav進(jìn)行了一番研究。自己寫了一個(gè)wav轉(zhuǎn)采樣率的功能。給大家分享一下,中間遇到了不少坑,解決的過(guò)程中還是蠻有意思的,希望大家能喜歡
    2022-12-12
  • Go anko實(shí)現(xiàn)支持腳本語(yǔ)言

    Go anko實(shí)現(xiàn)支持腳本語(yǔ)言

    anko是一個(gè)可以讓 Go 項(xiàng)目支持腳本語(yǔ)言的小工具,換句話說(shuō),就是可以給 Go 項(xiàng)目加點(diǎn)“腳本魔法”,下面就跟隨小編一起來(lái)學(xué)習(xí)一下他的具體使用吧
    2024-11-11
  • Go語(yǔ)言實(shí)現(xiàn)AOI區(qū)域視野管理流程詳解

    Go語(yǔ)言實(shí)現(xiàn)AOI區(qū)域視野管理流程詳解

    在游戲中,場(chǎng)景里存在大量的物體.如果我們把所有物體的變化都廣播給玩家.那客戶端很難承受這么大的壓力.因此我們肯定會(huì)做優(yōu)化.把不必要的信息過(guò)濾掉.如只關(guān)心玩家視野所看到的.減輕客戶端的壓力,給玩家更流暢的體驗(yàn)
    2023-03-03
  • goland配置自動(dòng)注釋的實(shí)現(xiàn)

    goland配置自動(dòng)注釋的實(shí)現(xiàn)

    本文主要介紹了goland配置自動(dòng)注釋的實(shí)現(xiàn),文中通過(guò)圖文示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08

最新評(píng)論

禹州市| 永登县| 莲花县| 绥化市| 莎车县| 永嘉县| 台南县| 营口市| 宁海县| 西平县| 常宁市| 德令哈市| 邵东县| 三原县| 杭锦后旗| 芦溪县| 通榆县| 遂昌县| 五原县| 青阳县| 资阳市| 定州市| 页游| 库尔勒市| 明星| 呼玛县| 昌吉市| 乐业县| 分宜县| 成安县| 古蔺县| 金寨县| 米泉市| 徐闻县| 渝中区| 新野县| 象山县| 新闻| 三亚市| 遵化市| 惠安县|