<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          time.Sleep(1) 后發(fā)生了什么

          共 5201字,需瀏覽 11分鐘

           ·

          2020-10-25 07:40

          Time.sleep(d duration)方法會(huì)阻塞一個(gè)協(xié)程的執(zhí)行直到d時(shí)間結(jié)束。

          實(shí)現(xiàn)原理很簡(jiǎn)單,但內(nèi)部代碼實(shí)現(xiàn)卻是大有文章,每個(gè)go版本的timer的實(shí)現(xiàn)都有所不同,本文基于go1.14,接下來(lái)分別從宏觀和圍觀介紹一遍主要調(diào)度過(guò)程。




          圖文演示


          下面介紹一種最簡(jiǎn)單的場(chǎng)景:

          首先存在多個(gè)goroutine,GT為有time.Sleep休眠的g,當(dāng)GT被調(diào)度到m上執(zhí)行時(shí),場(chǎng)景如下圖。




          此時(shí)執(zhí)行到了time.Sleep代碼,GT會(huì)與m解綁,同時(shí)將該GT的sleep時(shí)間等信息記錄到P的timers字段上,此時(shí)GT處于Gwaiting狀態(tài),不在運(yùn)行隊(duì)列上,調(diào)度器會(huì)調(diào)度一個(gè)新的G2到M上執(zhí)行。(在每次調(diào)度過(guò)程中,會(huì)檢查P里面記錄的定時(shí)器,看看有沒(méi)有要執(zhí)行的。)



          G2執(zhí)行完了,當(dāng)要進(jìn)行下一輪調(diào)度時(shí),調(diào)度器檢查自己記錄的定時(shí)器時(shí)發(fā)現(xiàn),GT到時(shí)間了,是時(shí)候執(zhí)行了。由于任務(wù)緊急,GT就會(huì)被強(qiáng)行插入到P的運(yùn)行隊(duì)列的對(duì)頭,保證能馬上被執(zhí)行到。


          接下來(lái)就會(huì)直接調(diào)度到GT執(zhí)行了,睡眠結(jié)束。接下來(lái)跟隨這個(gè)簡(jiǎn)單場(chǎng)景看一下源碼實(shí)現(xiàn)。



          階段一、進(jìn)入睡眠


          首先調(diào)用time.Sleep(1)會(huì)經(jīng)過(guò)編譯器識(shí)別//go:linkname鏈接進(jìn)入到runtime.timeSleep(n int)方法。

          ?

          //go:linkname timeSleep time.Sleepfunc timeSleep(ns int64) {    if ns <= 0 { //判斷入?yún)⑹欠裾?/span>      return   }      gp := getg() //獲取當(dāng)前的goroutine???t?:=?gp.timer //如果不存在timer,new一個(gè)   if t == nil {      t = new(timer)      gp.timer = t   }   t.f = goroutineReady  //后面喚醒時(shí)候會(huì)用到,修改goroutine狀態(tài)為goready   t.arg = gp   t.nextwhen = nanotime() + ns  //記錄上喚醒時(shí)間   gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)  //調(diào)用gopark掛起goroutine}



          resetForSleep作為一個(gè)函數(shù)入?yún)?,他的調(diào)用棧依次為resettimer(t, t.nextwhen)?? ->??modtimer(t, when, t.period, t.f, t.arg, t.seq)(后文會(huì)講到),在后面的modtimer里面會(huì)將timer定時(shí)器加入到當(dāng)前goroutine所在的p中,定時(shí)器在p中的結(jié)構(gòu)為一個(gè)四叉堆,最近的時(shí)間的放在最堆頂上,對(duì)于這個(gè)數(shù)據(jù)結(jié)構(gòu)沒(méi)有做深入研究。

          接下來(lái)看一下gopark中重要的部分。


          func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) { ???......省略了大部分代碼?????   mp.waitlock = lock  //由于runningG和p沒(méi)有連接,將timer賦值到當(dāng)前m上,后面會(huì)給到p   mp.waitunlockf = unlockf  //將函數(shù)付給m   ......???mcall(park_m) //將當(dāng)前的g停放}


          看一下gopark里面的mcall里面的回調(diào)函數(shù)park_m中的部分。
          ?func?park_m(gp?*g)?{    _g_ := getg()  //獲取當(dāng)前goroutine   ......
          casgstatus(gp, _Grunning, _Gwaiting) //將goroutine狀態(tài)設(shè)為waiting dropg()
          if fn := _g_.m.waitunlockf; fn != nil { //獲取到mresetForSleep函數(shù) ok := fn(gp, _g_.m.waitlock) //返回值是true _g_.m.waitunlockf = nil //清空該m的函數(shù)空間 _g_.m.waitlock = nil //... ...... } schedule() //觸發(fā)新的調(diào)速循環(huán),可執(zhí)行隊(duì)列中獲取g到m上進(jìn)行調(diào)度}


          看一下resetForSleep回調(diào)函數(shù),里面依次調(diào)用了resettimer(t, t.nextwhen)?? ->??modtimer(t, when, t.period, t.f, t.arg, t.seq),resettimer函數(shù)沒(méi)有什么重要信息,只負(fù)責(zé)返回一個(gè)true,看一下modtimer函數(shù)。
          func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) {     ...... loop:     for {       switch status = atomic.Load(&t.status); status {      ......      case timerNoStatus, timerRemoved:   //由于剛創(chuàng)建,所以timer為默認(rèn)值0,對(duì)應(yīng)timerNoStatus         mp = acquirem()         if atomic.Cas(&t.status, status, timerModifying) {            wasRemoved = true   //設(shè)置標(biāo)志位為true            break loop         }         releasem(mp)              badTimer()      }   }
          t.period = period???t.f?=?f?//上文傳過(guò)來(lái)的goroutineReady函數(shù),用于將g轉(zhuǎn)變?yōu)閞unnable狀態(tài) t.arg = arg //上文的g實(shí)例 t.seq = seq
          ???if?wasRemoved?{?//會(huì)執(zhí)行到此處 t.when = when ??????pp?:=?getg().m.p.ptr()?//獲取當(dāng)前的p的指針??????lock(&pp.timersLock)?//加鎖,為了并發(fā)安全,因?yàn)閠imer可以去其他的p偷取??????doaddtimer(pp,?t)?//添加定時(shí)器到當(dāng)前的p??????unlock(&pp.timersLock)?//解鎖??????if?!atomic.Cas(&t.status,?timerModifying,?timerWaiting)?{ //轉(zhuǎn)變到timerWaiting badTimer() } ??????......}


          當(dāng)觸發(fā)完gopark方法,會(huì)調(diào)用releasem(mp)方法釋放當(dāng)前goroutine與m的連接后,該goroutine脫離當(dāng)前的m掛起,進(jìn)入gwaiting狀態(tài),不在任何運(yùn)行隊(duì)列上。對(duì)應(yīng)上圖2。




          階段二、恢復(fù)執(zhí)行



          執(zhí)行的恢復(fù)會(huì)在shedule()或者findRunnable()函數(shù)上,內(nèi)部checkTimers(pp, 0)方法,該方法內(nèi)部會(huì)判斷p中timers堆頂?shù)?/span>定時(shí)器,如果時(shí)間到了的話(當(dāng)前時(shí)間大于計(jì)算的時(shí)間),調(diào)用?runtime.runOneTimer?,該方法里面會(huì)一系列調(diào)用到goready方法釋放阻塞的goroutine,并將該goroutine放到運(yùn)行隊(duì)列的第一個(gè)。

          接下來(lái)看一下checkTimers函數(shù):


          func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) { 
          ......省略掉調(diào)整計(jì)時(shí)器時(shí)間的一些步驟???lock(&pp.timersLock)?//加鎖 adjusttimers(pp) //調(diào)整計(jì)時(shí)器的時(shí)間 rnow = now if len(pp.timers) > 0 { if rnow == 0 { rnow = nanotime() } for len(pp.timers) > 0 {?????????if?tw?:=?runtimer(pp,?rnow);?tw?!=?0?{?//進(jìn)入runtimer方法,攜帶系統(tǒng)時(shí)間參數(shù)與處理器 if tw > 0 { pollUntil = tw } break } ran = true } }......}





          進(jìn)入runtimer方法,會(huì)查看p里面的堆頂?shù)亩〞r(shí)器,檢查是否需要執(zhí)行


          func runtimer(pp *p, now int64) int64 {     for {??????t?:=?pp.timers[0]?//遍歷堆頂?shù)亩〞r(shí)器     .......      switch s := atomic.Load(&t.status); s {      case timerWaiting:  //經(jīng)過(guò)time.Sleep的定時(shí)器會(huì)是waiting狀態(tài)         if t.when > now {  //判斷是否超過(guò)時(shí)間             // Not ready to run.            return t.when         }
          if !atomic.Cas(&t.status, s, timerRunning) { //修改計(jì)時(shí)器狀態(tài) continue?????????}?????????runOneTimer(pp,?t,?now)?//運(yùn)行該計(jì)時(shí)器函數(shù) return 0 ........




          接下來(lái)調(diào)用runOneTimer函數(shù)處理
          func runOneTimer(pp *p, t *timer, now int64) {  ........
          ???f?:=?t.f?//goready函數(shù)???arg?:=?t.arg //就是之前傳入的goroutine???seq?:=?t.seq? //默認(rèn)值0
          if t.period > 0 { ......... //由于period為默認(rèn)值0,會(huì)走else里面 } else { dodeltimer0(pp) //刪除該計(jì)時(shí)器在p中,該timer在0坐標(biāo)位 if !atomic.Cas(&t.status, timerRunning, timerNoStatus) { //設(shè)置為nostatus badTimer() } }....... unlock(&pp.timersLock)
          f(arg, seq) //執(zhí)行g(shù)oroutineReady方法,喚起等待的goroutine .........}



          看一下上面的f(arg,seq)即goroutineReady法的實(shí)現(xiàn),該函數(shù)的實(shí)現(xiàn)就是直接調(diào)用了goready方法喚起goroutine,對(duì)應(yīng)上圖3:

          func goroutineReady(arg interface{}, seq uintptr) {  ???goready(arg.(*g),?0)?//該處傳入的第二個(gè)參數(shù)代表調(diào)度到運(yùn)行隊(duì)列的位置,該處設(shè)置為0,說(shuō)明直接調(diào)度到運(yùn)行隊(duì)列即將要執(zhí)行的位置,等待被執(zhí)行。}



          另外,系統(tǒng)監(jiān)控sysmon函數(shù)也可以觸發(fā)定時(shí)器的調(diào)用,該函數(shù)是一個(gè)循環(huán)檢查系統(tǒng)中是否擁有應(yīng)該被運(yùn)行但是還在等待的定時(shí)器,并調(diào)度他們運(yùn)行。

          對(duì)于time.NewTimer函數(shù)等,實(shí)現(xiàn)方法也是大致相似,只是回調(diào)函數(shù)變成了sendTime函數(shù),該函數(shù)不會(huì)阻塞。調(diào)用該函數(shù)后,睡眠的goroutine會(huì)從channel中釋放并加入運(yùn)行隊(duì)列,有興趣可以自己研究一下。

          以上就是整個(gè)time.sleep的調(diào)度過(guò)程,你可以根據(jù)我總結(jié)的對(duì)照源碼一步一步看,肯定會(huì)加深印象,深入理解。



          參考文章


          【1】《Go計(jì)時(shí)器》https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-timer/

          【2】《Golang定時(shí)器底層實(shí)現(xiàn)剖析》https://www.cyhone.com/articles/analysis-of-golang-timer/



          推薦閱讀


          福利

          我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。


          瀏覽 199
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  经典国产三级在线 | 国产精品久久久久久久久免费挑花 | 国产精品久久久久久欧 | 国产乱伦黄色 | 亚洲男女激情91免费网站 |