golang定時器的精度

以 go1.23.3 linux/amd64 為例。

定時器示例代碼:

package mainimport ("context""fmt""time"
)var ctx context.Contextfunc main() {timeout := 600 * time.Secondctx, _ = context.WithTimeout(context.Background(), timeout)deadline, _ := ctx.Deadline()fmt.Println("process start", time.Now().Format(time.DateTime))fmt.Println("ctx deadline", deadline.Format(time.DateTime))go func() {defer func() {fmt.Println("goroutine exit", time.Now().Format(time.DateTime))}()for {select {case <-ctx.Done():fmt.Println("ctx.Done", time.Now().Format(time.DateTime))returndefault:fmt.Println("do something start", time.Now().Format(time.DateTime))time.Sleep(60 * time.Second)fmt.Println("do something end  ", time.Now().Format(time.DateTime))}}}()time.Sleep(timeout + 10*time.Second)fmt.Println("process exit", time.Now().Format(time.DateTime))
}

定時器創建流程:

定時器的類型為:runtime.timer,新建定時器會調用runtime.newTimer函數。

runtime.newTimer函數會調用func (t *timer) maybeAdd(),在此函數中將定時器放入ts堆中:

func (t *timer) maybeAdd() {// Note: Not holding any locks on entry to t.maybeAdd,// so the current g can be rescheduled to a different M and P// at any time, including between the ts := assignment and the// call to ts.lock. If a reschedule happened then, we would be// adding t to some other P's timers, perhaps even a P that the scheduler// has marked as idle with no timers, in which case the timer could// go unnoticed until long after t.when.// Calling acquirem instead of using getg().m makes sure that// we end up locking and inserting into the current P's timers.mp := acquirem()ts := &mp.p.ptr().timersts.lock()ts.cleanHead()t.lock()t.trace("maybeAdd")when := int64(0)wake := falseif t.needsAdd() {t.state |= timerHeapedwhen = t.whenwakeTime := ts.wakeTime()wake = wakeTime == 0 || when < wakeTimets.addHeap(t)}t.unlock()ts.unlock()releasem(mp)if wake {wakeNetPoller(when)}
}

入堆:

// addHeap adds t to the timers heap.
// The caller must hold ts.lock or the world must be stopped.
// The caller must also have checked that t belongs in the heap.
// Callers that are not sure can call t.maybeAdd instead,
// but note that maybeAdd has different locking requirements.
func (ts *timers) addHeap(t *timer) {assertWorldStoppedOrLockHeld(&ts.mu)// Timers rely on the network poller, so make sure the poller// has started.if netpollInited.Load() == 0 {netpollGenericInit()}if t.ts != nil {throw("ts set in timer")}t.ts = tsts.heap = append(ts.heap, timerWhen{t, t.when})ts.siftUp(len(ts.heap) - 1)if t == ts.heap[0].timer {ts.updateMinWhenHeap()}
}

timers堆為每P持有,保存P隊列中協程定義的定時器。

// A timers is a per-P set of timers.
type timers struct {// mu protects timers; timers are per-P, but the scheduler can// access the timers of another P, so we have to lock.mu mutex// heap is the set of timers, ordered by heap[i].when.// Must hold lock to access.heap []timerWhen// len is an atomic copy of len(heap).len atomic.Uint32// zombies is the number of timers in the heap// that are marked for removal.zombies atomic.Int32// raceCtx is the race context used while executing timer functions.raceCtx uintptr// minWhenHeap is the minimum heap[i].when value (= heap[0].when).// The wakeTime method uses minWhenHeap and minWhenModified// to determine the next wake time.// If minWhenHeap = 0, it means there are no timers in the heap.minWhenHeap atomic.Int64// minWhenModified is a lower bound on the minimum// heap[i].when over timers with the timerModified bit set.// If minWhenModified = 0, it means there are no timerModified timers in the heap.minWhenModified atomic.Int64
}type timerWhen struct {timer *timerwhen  int64
}

創建定時器堆棧如圖:

定時器觸發流程:

timers堆的定時器通過func (ts *timers) run(now int64) int64出堆并運行。

而檢查是否有定時器到期是通過函數func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool)中的func (ts *timers) wakeTime() int64進行的。

check函數和wakeTime函數的調度時機在runtime/proc.go文件中多處存在,如runtime.findRunnable()、runtime.stealWork(now int64)、runtime.schedule()等。

這種依賴協程調度、系統調用等觸發的定時器檢查,延遲時間最多可達到func sysmon()協程的間隔時間10ms。

觸發定時器堆棧如圖:

另外在新建定時器時,也會檢查timers堆頂部的定時器剩余時間,如果已經到期也會立刻通過runtime.wakeNetPoller(when int64)觸發runtime.netpoll(delay int64)返回,檢查是否存在可處理的event,然后進行timers堆的定時器check。

定時器精度小結:

golang內置的Timer定時器維護在用戶態,比較輕量,依賴協程調度、系統調用、event等來觸發時間到期檢查,延遲在10ms以內,精度不高。

定時器的觀測:

修改源碼創建多個ctx定時器:

package mainimport ("context""fmt""time"
)var ctx context.Contextfunc main() {timeout := 300 * time.Secondctx, _ = context.WithTimeout(context.Background(), timeout)ctx, _ = context.WithTimeout(ctx, 180*time.Second)deadline, _ := ctx.Deadline()fmt.Println("process start", time.Now().Format(time.DateTime))fmt.Println("ctx deadline", deadline.Format(time.DateTime))go func() {defer func() {fmt.Println("goroutine exit", time.Now().Format(time.DateTime))}()for {select {case <-ctx.Done():fmt.Println("ctx.Done", time.Now().Format(time.DateTime))returndefault:fmt.Println("do something start", time.Now().Format(time.DateTime))time.Sleep(5 * time.Second)fmt.Println("do something end  ", time.Now().Format(time.DateTime))}}}()time.Sleep(timeout + 10*time.Second)fmt.Println("process exit", time.Now().Format(time.DateTime))
}

dlv調試:

1、查看當前的定時器數量:

p runtime.allp[1].timers.heap 

2、查看每個定時器的超時時間:

p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)
p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)
p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)

3、調用其中一個定時器的回調函數:

call runtime.allp[1].timers.heap[0].timer.arg.(func())()

4、查看控制臺輸出:

共有3個定時器,分別是ctx的2個和主協程的time.Sleep,其中timers堆頂是180s的定時器。

在手工調用timers堆頂定時器的回調函數后,提前收到ctx.Done通知,程序提前退出。

如圖:

cancelCtx的父子關系:

繼續上面的例子:

1、查看ctx兩個定時器的回調函數是否一致:

p runtime.allp[1].timers.heap[0].timer.arg
p runtime.allp[1].timers.heap[1].timer.arg

2、查看父子cancelCtx變量內容:

#子ctx
p ctx
#父ctx
p ctx.cancelCtx.Context

3、觀測結果說明:

父子cancelCtx回調函數內部引用的外部變量context.timerCtx并不相同。

父子cancelCtx之間是嵌套關系,子嵌套(繼承)父。

最終使用的ctx為子ctx,子ctx的任一層父ctx的超時都會導致子ctx退出。

如圖:

父子cancelCtx的嵌套關系通過函數func (c *cancelCtx) propagateCancel(parent Context, child canceler)完成:

// propagateCancel arranges for child to be canceled when parent is.
// It sets the parent context of cancelCtx.
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {c.Context = parentdone := parent.Done()if done == nil {return // parent is never canceled}select {case <-done:// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault:}if p, ok := parentCancelCtx(parent); ok {// parent is a *cancelCtx, or derives from one.p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()return}if a, ok := parent.(afterFuncer); ok {// parent implements an AfterFunc method.c.mu.Lock()stop := a.AfterFunc(func() {child.cancel(false, parent.Err(), Cause(parent))})c.Context = stopCtx{Context: parent,stop:    stop,}c.mu.Unlock()return}goroutines.Add(1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))case <-child.Done():}}()
}

--end--

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78879.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78879.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78879.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

svn 遠程服務搜索功能

svn服務器沒有遠程搜索功能&#xff0c;靠人工檢索耗時耗力&#xff0c;當服務器文件過多時&#xff0c;全部checkout到本地檢索&#xff0c;耗時太久。 1. TortoiseSVN 安裝注意事項 下載官網地址&#xff1a;https://tortoisesvn.en.softonic.com/download 安裝時選中 co…

uniapp-商城-39-shop 購物車 選好了 進行訂單確認4 配送方式2 地址頁面

上面講基本的樣式和地址信息&#xff0c;但是如果沒有地址就需要添加地址&#xff0c;如果有不同的地址就要選地址。 來看看處理方式&#xff0c; 1、回顧 在delivery-layout中 methods:{goAddress(){uni.navigateTo({url:"/pagesub/pageshop/address/addrlist"})…

Linux命令-iostat

iostat 命令介紹 iostat 是一個用于監控 Linux 系統輸入/輸出設備加載情況的工具。它可以顯示 CPU 的使用情況以及設備和分區的輸入/輸出統計信息&#xff0c;對于診斷系統性能瓶頸&#xff08;如磁盤或網絡活動緩慢&#xff09;特別有用。 語法&#xff1a; iostat [options…

vue2關于Node.js17及以上報digital envelope錯誤的解決辦法

文章目錄 簡介錯誤原因解決方案設置環境變量修改package.json安裝舊版本Node.js更新依賴項更改加密設置 簡介 digital envelope routines::unsupported錯誤?通常發生在Node.js版本升級到17或更高版本后&#xff0c;因為這些版本開始使用OpenSSL 3.0&#xff0c;它對算法和密鑰…

LLM - Large Language Model

回顧2024&#xff1a;與LLM又相伴一年的經歷與思考 - 知乎萬字長文入門大語言模型&#xff08;LLM&#xff09; - 知乎“大模型本質就是兩個文件&#xff01;”特斯拉前AI總監爆火LLM科普&#xff0c;時長1小時&#xff0c;面向普通大眾 - 知乎大模型本質及趨勢剖析&#xff0c…

Linux 內核網絡協議棧中的關鍵數據結構:inet_skb_parm 與 ip_options

在 Linux 內核的網絡協議棧中,數據包的高效處理依賴于一系列精心設計的數據結構。這些結構體不僅需要存儲網絡數據的元信息,還需支持復雜的協議邏輯(如路由、分片、安全策略等)。本文聚焦兩個核心結構體 struct inet_skb_parm 和 struct ip_options,解析它們的設計原理、功…

如何修復卡在恢復模式下的 iPhone:簡短指南

Apple 建議使用恢復模式作為最后的手段&#xff0c;以便在 iPhone 啟動循環或顯示 Apple 標志時恢復 iPhone。這是解決持續問題的簡單方法&#xff0c;但您很少使用。但是&#xff0c;當您的 iPhone 卡住恢復模式本身時&#xff0c;您會怎么做&#xff1f;雖然 iPhone 卡在這種…

10前端項目----商品詳情頁/滾輪行為

商品詳情頁面 商品詳情組件發送請求獲取相應商品詳情信息組件展示數據 優化一下路由配置代碼滾輪自動置頂 商品詳情組件 路由配置 點擊商品進行跳轉—將Detail組件變成路由組件 從商品到詳情&#xff0c;肯定需要傳參(產品ID)告訴Detail是哪個商品&#xff0c;需要展示哪個商品…

DIFY 又跟新了,來到 1.3.0 版本,看正文

歡迎來到 1.3.0 版本&#xff01;添加了各種巧妙的功能、修復了錯誤&#xff0c;并帶來了一些新功能&#xff1a; 一、核心亮點&#xff1a; 結構化輸出 1、LLM 節點新增JSON Schema編輯器&#xff0c;確保大語言模型能夠返回符合預設格式的JSON數據。這一功能有助于提升數據…

git檢查提交分支和package.json的version版本是否一致

這里寫自定義目錄標題 一、核心實現步驟?1.安裝必要依賴?2.初始化 Husky?3.創建校驗腳本?4.配置 lint-staged?5.更新 Husky 鉤子? 三、工作流程說明?四、注意事項? 以下是基于 Git Hooks 的完整解決方案&#xff0c;通過 husky 和自定義腳本實現分支名與版本號一致性校…

react-navigation-draw抽屜導航

心得寫在前面分享給大家&#xff1a; 我的實現方法&#xff0c;并沒有完全安裝官網來做&#xff0c;而是進行了簡化&#xff0c;效果是一樣的。沒有按照官網說的修改metro.config.js文件&#xff0c;同時也沒有 react-native-gesture-handler 的安裝后&#xff0c;我們需要有條…

【計算機視覺】CV實戰項目-高分辨率遙感圖像語義分割:High-Resolution-Remote-Sensing-Semantic-Segmentation

高分辨率遙感圖像語義分割技術解析與實戰指南 項目背景與意義核心技術解析1. **膨脹預測&#xff08;Dilated Prediction&#xff09;**2. **后處理優化**3. **半監督學習&#xff1a;偽標簽&#xff08;Pseudo Labeling&#xff09;**4. **可視化與監控** 實戰指南&#xff1a…

免費送源碼:Java+SSM+MySQL 基于SSM開發的校園心理咨詢平臺系統的設計與實現 計算機畢業設計原創定制

目 錄 1 緒論 1 1.1 研究背景 1 1.2開發現狀 1 1.3論文結構與章節安排 2 2 校園心理咨詢平臺系統系統分析 3 2.1 可行性分析 3 2.1.1 技術可行性分析 3 2.1.2 經濟可行性分析 3 2.1.3 法律可行性分析 3 2.2 系統功能分析 3 2.2.1 功能性分析 4 2.2.2 非功能性分析…

學習筆記:Qlib 量化投資平臺框架 — GETTING STARTED

學習筆記&#xff1a;Qlib 量化投資平臺框架 — GETTING STARTED Qlib 是微軟亞洲研究院開源的一個面向人工智能的量化投資平臺&#xff0c;旨在實現人工智能技術在量化投資中的潛力&#xff0c;賦能研究&#xff0c;并創造價值&#xff0c;從探索想法到實施生產。Qlib 支持多種…

cmake qt 項目編譯

當前MAC 編譯命令 rm -rf build 刪除之前build記錄 mkdir build && cd build 重新生成build文件夾 cmake -DCMAKE_PREFIX_PATH"/usr/local/opt/qt" .. Cmake編譯指定我的qt路徑 cmake --build . 生成程序 程序生成后如此 第三方庫單獨下載 在CMakeLis…

Swift與iOS內存管理機制深度剖析

前言 內存管理是每一位 iOS 開發者都繞不開的話題。雖然 Swift 的 ARC&#xff08;自動引用計數&#xff09;極大簡化了開發者的工作&#xff0c;但只有深入理解其底層實現&#xff0c;才能寫出高效、健壯的代碼&#xff0c;避免各種隱蔽的內存問題。本文將從底層原理出發&…

【機器學習】?碳化硅器件剩余使用壽命稀疏數據深度學習預測

2025 年,哈爾濱工業大學的 Le Gao 等人基于物理信息深度學習(PIDL)方法,研究了在稀疏數據條件下碳化硅(SiC)MOSFET 的剩余使用壽命(RUL)預測問題,尤其關注了其在輻射環境下的可靠性。該研究團隊通過一系列實驗,采用 ??Co γ 射線作為輻射源,以 50rad/s 的劑量率照…

Spring Boot API版本控制實踐指南

精心整理了最新的面試資料和簡歷模板&#xff0c;有需要的可以自行獲取 點擊前往百度網盤獲取 點擊前往夸克網盤獲取 引言 在API迭代過程中&#xff0c;版本控制是保障系統兼容性的重要機制。合理的版本控制策略可以幫助開發團隊平滑過渡接口變更&#xff0c;同時支持多版本客…

AI 語音芯片賦能血壓計,4G Cat.1語音模組重構血壓監測體驗,重新定義 “智能健康管理

一、技術升級背景 全球老齡化進程加速與慢性病管理需求激增的背景下&#xff0c;傳統血壓計面臨三大核心痛點&#xff1a; 操作門檻高&#xff1a;老年群體對復雜按鍵操作適應性差&#xff0c;誤觸率達42%&#xff08;參考WHO數據&#xff09; 數據孤島化&#xff1a;87%的居家…

WebServiceg工具

WebServiceg工具 幾年前的簡單記錄一下。 /*** 調用webService 接口返回字符串* param asmxUrl 提供接口的地址 https://app.***.**.cn/Ser.asmx* param waysName 設置要調用哪個方法 上面接口打開后需要調用的方法名字 * param params 請求的參數 參數* return*/…