色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Go timer的工作原理是什么?Go timer是如何被調(diào)度的?

冬至子 ? 來源:HHFCodeRv ? 作者:haohongfan ? 2023-07-17 15:16 ? 次閱讀

定時器不管是業(yè)務(wù)開發(fā),還是基礎(chǔ)架構(gòu)開發(fā),都是繞不過去的存在,由此可見定時器的重要程度。

我們不管用 NewTimer, timer.After,還是 timer.AfterFun 來初始化一個 timer, 這個 timer 最終都會加入到一個全局 timer 堆中,由 Go runtime 統(tǒng)一管理。

全局的 timer 堆也經(jīng)歷過三個階段的重要升級。

  • Go 1.9 版本之前,所有的計時器由全局唯一的四叉堆維護(hù),協(xié)程間競爭激烈。
  • Go 1.10 - 1.13,全局使用 64 個四叉堆維護(hù)全部的計時器,沒有本質(zhì)解決 1.9 版本之前的問題
  • Go 1.14 版本之后,每個 P 單獨維護(hù)一個四叉堆。

Go 1.14 以后的 timer 性能得到了質(zhì)的飛升,不過伴隨而來的是 timer 成了 Go 里面最復(fù)雜、最難梳理的數(shù)據(jù)結(jié)構(gòu)。本文不會詳細(xì)分析每一個細(xì)節(jié),我們從大體來了解 Go timer 的工作原理

1. 使用場景

Go timer 在我們代碼中會經(jīng)常遇到。

場景1:RPC 調(diào)用的防超時處理(下面代碼節(jié)選 dubbogo)

func (c *Client) Request(request *remoting.Request, timeout time.Duration, response *remoting.PendingResponse) error {
    _, session, err := c.selectSession(c.addr)
    // .. 省略
    if totalLen, sendLen, err = c.transfer(session, request, timeout); err != nil {
        if sendLen != 0 && totalLen != sendLen {
          // .. 省略
        }
        return perrors.WithStack(err)
    }

    // .. 省略
    select {
    case < -getty.GetTimeWheel().After(timeout):
        return perrors.WithStack(errClientReadTimeout)
    case < -response.Done:
        err = response.Err
    }
    return perrors.WithStack(err)
}

場景2:Context 的超時處理

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()
    go doSomething()
    
    select {
    case < -ctx.Done():
        fmt.Println("main", ctx.Err())
    }
}

2. 圖解源碼

2.1 四叉堆原理

timer 的全局堆是一個四叉堆,特別是 Go 1.14 之后每個 P 都會維護(hù)著一個四叉堆,減少了 Goroutine 之間的并發(fā)問題,提升了 timer 了性能。

四叉堆其實就是四叉樹,Go timer 是如何維護(hù)四叉堆的呢?

  • Go runtime 調(diào)度 timer 時,觸發(fā)時間更早的 timer,要減少其查詢次數(shù),盡快被觸發(fā)。所以四叉樹的父節(jié)點的觸發(fā)時間是一定小于子節(jié)點的。
  • 四叉樹顧名思義最多有四個子節(jié)點,為了兼顧四叉樹插、刪除、重排速度,所以四個兄弟節(jié)點間并不要求其按觸發(fā)早晚排序。

這里用兩張動圖簡單演示下 timer 的插入和刪除

把 timer 插入堆

圖片

把 timer 從堆中刪除

圖片

2.2 timer 是如何被調(diào)度的?

  • 調(diào)用 NewTimer,timer.After, timer.AfterFunc 生產(chǎn) timer, 加入對應(yīng)的 P 的堆上。
  • 調(diào)用 timer.Stop, timer.Reset 改變對應(yīng)的 timer 的狀態(tài)。
  • GMP 在調(diào)度周期內(nèi)中會調(diào)用 checkTimers ,遍歷該 P 的 timer 堆上的元素,根據(jù)對應(yīng) timer 的狀態(tài)執(zhí)行真的操作。

圖片

2.3 timer 是如何加入到 timer 堆上的?

把 timer 加入調(diào)度總共有下面幾種方式:

  • 通過 NewTimer, time.After, timer.AfterFunc 初始化 timer 后,相關(guān) timer 就會被放入到對應(yīng) p 的 timer 堆上。
  • timer 已經(jīng)被標(biāo)記為 timerRemoved,調(diào)用了 timer.Reset(d),這個 timer 也會重新被加入到 p 的 timer 堆上
  • timer 還沒到需要被執(zhí)行的時間,被調(diào)用了 timer.Reset(d),這個 timer 會被 GMP 調(diào)度探測到,先將該 timer 從 timer 堆上刪除,然后重新加入到 timer 堆上
  • STW 時,runtime 會釋放不再使用的 p 的資源,p.destroy()->timer.moveTimers,將不再被使用的 p 的 timers 上有效的 timer(狀態(tài)是:timerWaiting,timerModifiedEarlier,timerModifiedLater) 都重新加入到一個新的 p 的 timer 上

2.4 Reset 時 timer 是如何被操作的?

Reset 的目的是把 timer 重新加入到 timer 堆中,重新等待被觸發(fā)。不過分為兩種情況:

  • 被標(biāo)記為 timerRemoved 的 timer,這種 timer 是已經(jīng)從 timer 堆上刪除了,但會重新設(shè)置被觸發(fā)時間,加入到 timer 堆中
  • 等待被觸發(fā)的 timer,在 Reset 函數(shù)中只會修改其觸發(fā)時間和狀態(tài)(timerModifiedEarlier或timerModifiedLater)。這個被修改狀態(tài)的 timer 也同樣會被重新加入到 timer堆上,不過是由 GMP 觸發(fā)的,由 checkTimers 調(diào)用 adjusttimers 或者 runtimer 來執(zhí)行的。

圖片

2.5 Stop 時 timer 是如何被操作的?

time.Stop 為了讓 timer 停止,不再被觸發(fā),也就是從 timer 堆上刪除。不過 timer.Stop 并不會真正的從 p 的 timer 堆上刪除 timer,只會將 timer 的狀態(tài)修改為 timerDeleted。然后等待 GMP 觸發(fā)的 adjusttimers 或者 runtimer 來執(zhí)行。

真正刪除 timer 的函數(shù)有兩個 dodeltimer,dodeltimer0。

圖片

2.6 Timer 是如何被真正執(zhí)行的?

timer 的真正執(zhí)行者是 GMP。GMP 會在每個調(diào)度周期內(nèi),通過 runtime.checkTimers 調(diào)用 timer.runtimer(). timer.runtimer 會檢查該 p 的 timer 堆上的所有 timer,判斷這些 timer 是否能被觸發(fā)。

如果該 timer 能夠被觸發(fā),會通過回調(diào)函數(shù) sendTime 給 Timer 的 channel C 發(fā)一個當(dāng)前時間,告訴我們這個 timer 已經(jīng)被觸發(fā)了。

如果是 ticker 的話,被觸發(fā)后,會計算下一次要觸發(fā)的時間,重新將 timer 加入 timer 堆中。

圖片

3. Timer 使用中的坑

確實 timer 是我們開發(fā)中比較常用的工具,但是 timer 也是最容易導(dǎo)致內(nèi)存泄露,CPU 狂飆的殺手之一。

不過仔細(xì)分析可以發(fā)現(xiàn),其實能夠造成問題就兩個方面:

  • 錯誤創(chuàng)建很多的 timer,導(dǎo)致資源浪費
  • 由于 Stop 時不會主動關(guān)閉 C,導(dǎo)致程序阻塞

3.1 錯誤創(chuàng)建很多 timer,導(dǎo)致資源浪費

func main() {
    for {
        // xxx 一些操作
        timeout := time.After(30 * time.Second)
        select {
        case < - someDone:
            // do something
        case < -timeout:
            return
        }
    }
}

上面這段代碼是造成 timer 異常的最常見的寫法,也是我們最容易忽略的寫法。

造成問題的原因其實也很簡單,因為 timer.After 底層是調(diào)用的 timer.NewTimer,NewTimer 生成 timer 后,會將 timer 放入到全局的 timer 堆中。

for 會創(chuàng)建出來數(shù)以萬計的 timer 放入到 timer 堆中,導(dǎo)致機(jī)器內(nèi)存暴漲,同時不管 GMP 周期 checkTimers,還是插入新的 timer 都會瘋狂遍歷 timer 堆,導(dǎo)致 CPU 異常。

要注意的是,不只 time.After 會生成 timer, NewTimer,time.AfterFunc 同樣也會生成 timer 加入到 timer 中,也都要防止循環(huán)調(diào)用。

解決辦法: 使用 time.Reset 重置 timer,重復(fù)利用 timer。

我們已經(jīng)知道 time.Reset 會重新設(shè)置 timer 的觸發(fā)時間,然后將 timer 重新加入到 timer 堆中,等待被觸發(fā)調(diào)用。

func main() {
    timer := time.NewTimer(time.Second * 5)    
    for {
        timer.Reset(time.Second * 5)

        select {
        case < - someDone:
            // do something
        case < -timer.C:
            return
        }
    }
}

3.2 程序阻塞,造成內(nèi)存或者 goroutine 泄露

func main() {
    timer1 := time.NewTimer(2 * time.Second)
    < -timer1.C
    println("done")
}

上面的代碼可以看出來,只有等待 timer 超時 "done" 才會輸出,原理很簡單:程序阻塞在 <-timer1.C 上,一直等待 timer 被觸發(fā)時,回調(diào)函數(shù) time.sendTime 才會發(fā)送一個當(dāng)前時間到 timer1.C 上,程序才能繼續(xù)往下執(zhí)行。

不過使用 timer.Stop 的時候就要特別注意了,比如:

func main() {
    timer1 := time.NewTimer(2 * time.Second)
    go func() {
        timer1.Stop()
    }()
    < -timer1.C

    println("done")
}

程序就會一直死鎖了,因為 timer1.Stop 并不會關(guān)閉 channel C,使程序一直阻塞在 timer1.C 上。

上面這個例子過于簡單了,試想下如果 <- timer1.C 是阻塞在子協(xié)程中,timer 被的 Stop 方法被調(diào)用,那么子協(xié)程可能就會被永遠(yuǎn)的阻塞在那里,造成 goroutine 泄露,內(nèi)存泄露。

Stop 的正確的使用方式:

func main() {
    timer1 := time.NewTimer(2 * time.Second)
    go func() {
        if !timer1.Stop() {
            < -timer1.C
        }
    }()

    select {
    case < -timer1.C:
        fmt.Println("expired")
    default:
    }
    println("done")
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    19312

    瀏覽量

    230038
  • GMP
    GMP
    +關(guān)注

    關(guān)注

    0

    文章

    11

    瀏覽量

    8981
  • RPC
    RPC
    +關(guān)注

    關(guān)注

    0

    文章

    111

    瀏覽量

    11540
  • 觸發(fā)器
    +關(guān)注

    關(guān)注

    14

    文章

    2000

    瀏覽量

    61187
  • 狀態(tài)機(jī)
    +關(guān)注

    關(guān)注

    2

    文章

    492

    瀏覽量

    27552
收藏 人收藏

    評論

    相關(guān)推薦

    NanoSync Timer 介紹

    Timer
    橙群微電子
    發(fā)布于 :2023年11月02日 10:07:13

    go語言能做什么工作

    Go語言主要用作服務(wù)器端開發(fā),其定位是用來開發(fā)“大型軟件”的,適合于很多程序員一起開發(fā)大型軟件,并且開發(fā)周期長,支持云計算的網(wǎng)絡(luò)服務(wù)。Go語言能夠讓程序員快速開發(fā),并且在軟件不斷的增長過程中,它能
    發(fā)表于 03-22 15:03

    s3c2410 Timer工作原理

    Width Modulation—— PWM(脈寬調(diào)制 )。Timer4是一個內(nèi)部定時器(internal timer),他沒有輸出引腳(output pins)。 下面是Timer工作原
    發(fā)表于 08-20 06:21

    555 Timer PRO and Lite

    555 Timer PRO provides an array of design wizards, circuit blocks and information panels
    發(fā)表于 05-09 19:06 ?86次下載

    IO_SOFT_UART_USE_TIMER

    IO_SOFT_UART_USE_TIMER_cIO_SOFT_UART_USE_TIMER_cIO_SOFT_UART_USE_TIMER_c
    發(fā)表于 12-29 10:54 ?0次下載

    通用定時器(Timer)

    1、Timer2 和 Timer3。它們的用法是相同的:每 個 Timer 模塊都可以配置為一個 32 位定時器或一個 32 位 RTC 定時器;也可以拆分為兩個 16 位的定時/計數(shù)器 TimerA 和 TimerB,它們
    發(fā)表于 01-13 16:34 ?23次下載

    基于8051的Proteus仿真-TIMER0與TIMER1控制

    基于8051的Proteus仿真-TIMER0與TIMER1控制條形LED
    發(fā)表于 09-01 23:32 ?15次下載

    Firefly關(guān)于TIMER 使用簡介

    ~pmutimer1), 我們主要用到的是Timers(timer0-timer11)時鐘頻率為24MHZ ,工作模式有 free-running 和 user-defined count 模式
    的頭像 發(fā)表于 11-20 11:17 ?1447次閱讀
    Firefly關(guān)于<b class='flag-5'>TIMER</b> 使用簡介

    fireflyAIO-3399C主板TIMER介紹

    ~pmutimer1), 我們主要用到的是Timers(timer0-timer11)時鐘頻率為24MHZ ,工作模式有 free-running 和 user-defined count 模式
    的頭像 發(fā)表于 12-09 16:26 ?1550次閱讀
    fireflyAIO-3399C主板<b class='flag-5'>TIMER</b>介紹

    fireflyAIO-3399J主板TIMER使用簡介

    ~pmutimer1), 我們主要用到的是Timers(timer0-timer11)時鐘頻率為24MHZ ,工作模式有 free-running 和 user-defined count 模式
    的頭像 發(fā)表于 12-24 10:25 ?1499次閱讀
    fireflyAIO-3399J主板<b class='flag-5'>TIMER</b>使用簡介

    使用單片機(jī)實現(xiàn)TIMER0和TIMER1與TIMER2實現(xiàn)外部信號計數(shù)與顯示的程序

    本文檔的主要內(nèi)容詳細(xì)介紹的是使用單片機(jī)實現(xiàn)TIMER0和TIMER1與TIMER2實現(xiàn)外部信號計數(shù)與顯示的C語言程序免費下載。
    發(fā)表于 03-26 16:42 ?26次下載

    詳解剖析Go語言調(diào)度模型的設(shè)計

    golang的MPG調(diào)度模型是保障Go語言效率高的一個重要特性,本文詳細(xì)介紹了Go語言調(diào)度模型的設(shè)計。 前言 Please remember that at the end of th
    的頭像 發(fā)表于 07-26 10:12 ?2090次閱讀
    詳解剖析<b class='flag-5'>Go</b>語言<b class='flag-5'>調(diào)度</b>模型的設(shè)計

    vim-go Vim的Go開發(fā)插件

    ./oschina_soft/vim-go.zip
    發(fā)表于 05-24 09:42 ?1次下載
    vim-<b class='flag-5'>go</b> Vim的<b class='flag-5'>Go</b>開發(fā)插件

    Go開源13周年 2022發(fā)布更多改變的Go 1.18 和 Go 1.19版本

    還有 Go 工作區(qū)、模糊測試等重要功能。 泛型是社區(qū)呼聲最大的特性,也是今年 Go 最重大的特性,它為 Go 添加了參數(shù)多態(tài)性,以允許編寫適用于
    的頭像 發(fā)表于 11-17 16:37 ?1240次閱讀

    如何讓Python和Go互相調(diào)度

    我們曾經(jīng)研究過如何讓Python和Go互相調(diào)度,當(dāng)時發(fā)現(xiàn),將Go語言寫的模塊打包成動態(tài)鏈接庫,就能在Python中進(jìn)行調(diào)度: 優(yōu)劣互補(bǔ)! Python+
    的頭像 發(fā)表于 11-02 11:24 ?626次閱讀
    如何讓Python和<b class='flag-5'>Go</b>互相<b class='flag-5'>調(diào)度</b>
    主站蜘蛛池模板: 野花香在线观看免费观看大全动漫 | 短篇合集纯肉高H深陷骚| 榴莲推广APP网站入口官网| 亚洲中文字幕日本在线观看| 国产亚洲精品首页在线播放| 午夜免费福利小电影| 贵妃高h荡肉呻吟np杨玉环 | 精品欧美一区二区三区久久久| 亚洲成av人影院| 国产亚洲精品福利视频| 午夜一级毛片看看| 国产精品嫩草影院| 无码中文字幕热热久久| 国产精品VIDEOSSEX久久发布| 色姊姊真舒服| 国产成人无码一区AV在线观看| 肉动漫3D卡通无修在线播放| 永久免费在线视频| 精品久久免费视频| 亚洲伊人久久大香线蕉综合图片| 动漫美女被到爽了流| 美女脱内衣裸身尿口露出来 | 亚洲 日韩 国产 中文视频| 国产欧美一区二区三区久久 | 欧美日韩高清一区二区三区| 月夜直播免费观看全集| 久久性综合亚洲精品电影网| 最新毛片网| 男人插女人动态| www.日本 高清.com| 日本艳妓BBW高潮一19| 刮伦人妇A极一片| 午夜精品久久久久久久99蜜桃| 国产欧美另类久久久精品免费| 亚洲AV怡红院影院怡春院| 黑人开嫩苞| 在线观看亚洲AV无码每日更新| 么么哒日本影院| BT7086福利二区最新| 妺妺窝人体色777777野大粗| Chineseman瘦老头77|