CyberRT(apollo) 定時器模塊簡述及bug分析

timer 模塊

timer的定義,cyberrt中timer模塊用于設置定時器任務,字面意思,設置設置定時周期及出發頻次(周期 or oneshot),到達指定時間時間觸發callback

time wheel

時鐘節拍輪,常見的定時器設計,例如ucos中的定時器,Linux Crontab等,cyberrt也是采用了時鐘輪

時間輪(TimingWheel)簡單來說,是一個 存儲定時任務的循環隊列,隊列中的每個元素都可以放置一個定時任務列表(TimeBucket) 。TimeBucket 是一個list,鏈表中的每一項表示的都是定時任務(TimerTask)。

時鐘輪示意圖

alt text

類圖

在這里插入圖片描述

@startuml
class Timer{
+explicit Timer(TimerOption opt)
+void Start()
+void Stop()
-TimerOption timer_opt_;
-TimingWheel* timing_wheel_ = nullptr;
-std::shared_ptr<TimerTask> task_;
}
class TimingWheel {- TimerBucket work_wheel_[WORK_WHEEL_SIZE]- TimerBucket assistant_wheel_[ASSISTANT_WHEEL_SIZE]- std::thread tick_thread_+ ~TimingWheel()+ void Start()+ void Shutdown()+ void Tick()+ void AddTask(const std::shared_ptr<TimerTask>& task)  + void TickFunc()}
class TimerBucket {- std::mutex mutex_- std::list<std::weak_ptr<TimerTask>> task_list_+ void AddTask(const std::shared_ptr<TimerTask>& task)+ std::mutex& mutex()+ std::list<std::weak_ptr<TimerTask>>& task_list()
}struct TimerTask {+ TimerTask(uint64_t timer_id)+ uint64_t timer_id_+ std::function<void()> callback+ uint64_t interval_ms+ uint64_t remainder_interval_ms+ uint64_t next_fire_duration_ms+ int64_t accumulated_error_ns+ uint64_t last_execute_time_ns+ std::mutex mutex
}
class TimerOption {+ uint32_t period+ std::function<void()> callback+ bool oneshot+ TimerOption(uint32_t period, std::function<void()> callback, bool oneshot)+ TimerOption()
}note left of Timer外部主要調用類,定時器對象實體
end notenote left of TimerOption配置參數,主要作為入參構造timer,用于配置定時器
end notenote left of TimingWheel環形隊列,cyberrt內部實現為二級時鐘節拍輪,單例
end notenote left of TimerBucket環形隊列中的元素,為鏈表,存儲std::weak_ptr<TimerTask>,等價線程安全的list<std::weak_ptr<TimerTask>>
end noteTimer --> TimerOption : 入參依賴 
Timer --> TimingWheel : 成員依賴 
TimingWheel --> TimerBucket : 成員依賴 
TimerBucket --> TimerTask : 成員依賴 @enduml

實現原理

timingwheel 中的tick線程用于統計時間時間,并獲取指向的時鐘節拍論里面的元素進行任務觸發,并將任務丟到cyberrt的攜程池里運行,timewheel的實現基本都大同小異,這里不細說。

bug

bug主要是call back的生命周期管理問題,在代碼中其實已經考慮了一部分(shared_ptr+weak_ptr)已經保證了一部分的懸空指針的問題,但是不完全。

 bool Timer::InitTimerTask() {task_.reset(new TimerTask(timer_id_));task_->interval_ms = timer_opt_.period;task_->next_fire_duration_ms = task_->interval_ms;if (timer_opt_.oneshot) {std::weak_ptr<TimerTask> task_weak_ptr = task_;task_->callback = [callback = this->timer_opt_.callback, task_weak_ptr]() {auto task = task_weak_ptr.lock();if (task) {std::lock_guard<std::mutex> lg(task->mutex);callback();}};} else {std::weak_ptr<TimerTask> task_weak_ptr = task_;task_->callback = [callback = this->timer_opt_.callback, task_weak_ptr]() {auto task = task_weak_ptr.lock();if (!task) {return;}XXXX //省略TimingWheel::Instance()->AddTask(task);};}return true;
}void TimingWheel::Tick() {auto& bucket = work_wheel_[current_work_wheel_index_];{std::lock_guard<std::mutex> lock(bucket.mutex());auto ite = bucket.task_list().begin();while (ite != bucket.task_list().end()) {auto task = ite->lock();if (task) {ADEBUG << "index: " << current_work_wheel_index_<< " timer id: " << task->timer_id_;auto* callback =reinterpret_cast<std::function<void()>*>(&(task->callback));cyber::Async([this, callback] {if (this->running_) {(*callback)();}});}ite = bucket.task_list().erase(ite);}}
}

以上代碼,構建TimerTask添加到timing wheel中,task為share_ptr,所有權歸屬timer對象很合理,傳遞weak_ptr對象給timingWhee,timer對象釋放后,節拍輪里面的weak_ptr不再有效進行不必要的觸發,這也很正確,合理的考慮了timer對象持有的task和timingwheel中task的異步生命周期管理的問題。

但是在tick函數中,將觸發的task放到異步協程池運行的時候則出現問題了,未考慮異步生命周期的問題。

現在假設我們有這樣一個場景

創建了一個10ms的周期timer,經過第一個10ms時觸發了定時器并將回掉丟到協程池中并推出tick,假設當時協程池比較繁忙,有恰巧我們迅速釋放掉timer對象或者stop掉timer對象,you lose,程序boom了。

因為傳遞task這個share_ptr對象里的callback 成員函數進行了異步調用,tick函數內雖然正確獲取了task,退出該函數,task引用計數-1,于此同時我們stop了timer 對象s或者 析構了對象,time持有的task,引用再次-1歸0,tick函數內cyber::Async雖然正確獲取了task的callback,但此時,已經是懸空指針了。

修改

知道了原因,改起來也很方便,無非就是異步調用指針的管理而已。

void TimingWheel::Tick() {auto& bucket = work_wheel_[current_work_wheel_index_];{std::lock_guard<std::mutex> lock(bucket.mutex());auto ite = bucket.task_list().begin();while (ite != bucket.task_list().end()) {auto task = ite->lock();if (task) {ADEBUG << "index: " << current_work_wheel_index_<< " timer id: " << task->timer_id_;// auto* callback =//   reinterpret_cast<std::function<void()>*>(&(task->callback));cyber::Async([this, weakTask = std::weak_ptr<TimerTask>(task)] {auto task = weakTask->lock();if (this->running_ && task ) {task->callback();}});}ite = bucket.task_list().erase(ite);}}
}

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

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

相關文章

java八股文之消息中間件

1.RabbitMQ如何保證消息不丟失 開啟生產者確認機制&#xff0c;確保生產者的消息能到達隊列開啟持久化功能&#xff0c;確保消息未消費前在隊列中不會丟失&#xff08;交換機&#xff0c;隊列&#xff0c;消息都需要開啟持久化功能&#xff09;開啟消費者確認機制為auto,由spr…

Win7重裝不翻車!ISO鏡像安全下載渠道+BIOS設置避雷手冊

一、寫在前面&#xff1a;為什么你需要這份教程&#xff1f; 當電腦頻繁藍屏、系統崩潰甚至無法開機時&#xff0c;重裝系統可能是最后的救命稻草。但市面上的教程往往存在三大痛點&#xff1a; ?? 鏡像來源不明導致系統被植入后門 ?? 啟動盤制作失敗反復折騰 ?? 操作失…

大學至今的反思與總結

現在是2025年的3月5日&#xff0c;我大三下學期。 自大學伊始&#xff0c;我便以考研作為自己的目標&#xff0c;有時還會做自己考研上岸頭部985,211&#xff0c;offer如潮水般涌來的美夢。 但是我卻忽略了一點&#xff0c;即便我早早下定了決心去考研&#xff0c;但并沒有早…

SpringBoot 全局異常處理

文章目錄 異常處理全局異常處理(推薦)局部異常處理高級技巧設置返回狀態碼處理404異常異常處理 全局異常處理(推薦) 創建一個全局異常處理類,使用 @RestControllerAdvice 注解標記。 在方法上使用 @ExceptionHandler 聲明當前方法可處理的異常類型。當系統發生異常時,…

【四.RAG技術與應用】【11.阿里云百煉應用(上):RAG在云端的實踐】

一、為什么需要RAG?大模型的“知識困境”與破局之道 大模型雖然“博學”,但它的知識庫存在兩個致命短板: 缺乏私有知識:比如企業內部的產品手冊、客戶數據、行業報告等;知識更新滯后:大模型的訓練數據往往停留在某個時間點,無法實時獲取最新信息(比如今天的股票行情或…

使用wifi連接手機adb進行調試|不使用數據線adb調試手機|找應用錯誤日志和操作日志

手機在開發者選項里要開啟無線調試 在手機設置中查看WiFi的IP地址 設置 -> WLAN -> 已連接的WiFi -> IP地址 使用手機的IP地址連接 adb connect 192.168.1.12:xxxxx 檢查連接狀態 adb devices 斷開特定設備 adb disconnect 192.168.x.x:xxxxx 斷開所有設備 …

mapbox高階,結合threejs(threebox)添加三維球體

????? 主頁: gis分享者 ????? 感謝各位大佬 點贊?? 收藏? 留言?? 加關注?! ????? 收錄于專欄:mapbox 從入門到精通 文章目錄 一、??前言1.1 ??mapboxgl.Map 地圖對象1.2 ??mapboxgl.Map style屬性1.3 ??threebox Sphere靜態對象二、??使用t…

游戲引擎學習第140天

回顧并為今天的內容做準備 目前代碼的進展到了聲音混音的部分。昨天我詳細解釋了聲音的處理方式&#xff0c;聲音在技術上是一個非常特別的存在&#xff0c;但在游戲中進行聲音混音的需求其實相對簡單明了&#xff0c;所以今天的任務應該不會太具挑戰性。 今天我們會編寫一個…

golang并發編程如何學習

《掌握 Golang 并發編程的通關秘籍》 在當今的編程世界中&#xff0c;Golang 并發編程正以其獨特的魅力和強大的能力吸引著眾多開發者。然而&#xff0c;對于許多小伙伴來說&#xff0c;如何學好這門技術卻成了一個頭疼的問題。別擔心&#xff0c;今天就讓我來為大家揭開 Gola…

SpringMVC學習(controller層加載控制與(業務、功能)bean加載控制、Web容器初始化配置類)(3)

目錄 一、SpringMVC、Spring的bean加載控制。 &#xff08;1&#xff09;實際開發的包結構層次。 &#xff08;2&#xff09;如何"精準"控制兩個容器分別加載各自bean。(分析) <1>SpringMVC相關bean加載控制。(方法) <2>Spring相關bean加載控制。(方法) …

fastapi+mysql實現增刪改查

說明&#xff1a; 我計劃用python的fastapi框架&#xff0c;實現操作MySQL數據庫的表&#xff0c;實現增刪改查的操作&#xff0c;并且在postman里面測試 step1: 安裝數據庫依賴 pip install fastapi uvicorn pymysqlstep2:C:\Users\Administrator\PycharmProjects\FastAPIPro…

Linux系統之配置HAProxy負載均衡服務器

Linux系統之配置HAProxy負載均衡服務器 前言一、HAProxy介紹1.1 HAProxy簡介1.2 主要特點1.3 使用場景二、本次實踐介紹2.1 本次實踐簡介2.2 本次實踐環境規劃三、部署兩臺web服務器3.1 運行兩個Docker容器3.2 編輯測試文件3.3 訪問測試四、安裝HAProxy4.1 更新系統軟件源4.2 安…

CS144 Lab Checkpoint 2: the TCP receiver

Overview TCPReceiver 從對等的sender接收消息&#xff0c;使用 receive() 方法&#xff0c;然后調用 Reassembler() 方法&#xff0c;后者寫入 ByteStream 中 然后應用程序從 ByteSteam 中讀取。 同時&#xff0c;TCPReceiver 還會通過 send() 方法給sender發送消息&#xff…

Spring Boot 3.x 核心注解詳解與最佳實踐

Spring Boot 3.x 核心注解詳解與最佳實踐 前言 隨著Spring Boot 3.x的正式發布&#xff0c;這個基于Spring Framework 6的里程碑版本帶來了諸多新特性。本文將深入剖析Spring Boot 3.x的核心注解體系&#xff0c;結合代碼示例講解其作用及使用場景&#xff0c;助您快速掌握新…

PHP之常量

在你有別的編程語言的基礎下&#xff0c;你想學習PHP&#xff0c;可能要了解的一些關于常量的信息。 PHP中的常量不用指定數據類型&#xff0c;可以使用兩次方法定義。 使用const //定義常量 const B 2; echo B . PHP_EOL;使用define define("A", 1); echo A . P…

計算機網絡——子網掩碼

一、子網掩碼是什么&#xff1f;它長什么樣&#xff1f; 子網掩碼的定義 子網掩碼是一個32位的二進制數字&#xff0c;與IP地址“配對使用”&#xff0c;用于標識IP地址中哪部分屬于網絡地址&#xff0c;哪部分屬于主機地址。 示例&#xff1a;IP地址 192.168.1.10&#xff0c;…

Tomcat-web服務器介紹以及安裝部署

一、Tomcat簡介 Tomcat是Apache軟件基金會&#xff08;Apache Software Foundation&#xff09;的Jakarta 項目中的一個核心項目&#xff0c;由Apache、Sun和其他一些公司及個人共同開發而成。 Tomcat服務器是一個免費的開放源代碼的Web應用服務器&#xff0c;屬于輕量級應用…

分布式存儲—— HBase數據模型 詳解

目錄 1.3 HBase數據模型 1.3.1 兩類數據模型 1.3.2 數據模型的重要概念 1.3.3 數據模型的操作 1.3.4 數據模型的特殊屬性 1.3.5 CAP原理與最終一致性 1.3.6 小結 本文章參考、總結于學校教材課本《HBase開發與應用》 1.3 HBase數據模型 在開始學習HBase之前非常…

android中activity1和activity2中接收定時消息

android中activity1和activity2中接收定時消息 業務類 import java.util.Timer; import java.util.TimerTask;public class MyAnager {private MyAnager() {}private static MyAnager instance;//回調接口onRecvTaskpublic interface OnMsgListener {void onRecvTask(String a…

BitMap實現用戶簽到、UV統計

1. Redis 的 BitMap 概述 在 Redis 中&#xff0c;BitMap 并非一種獨立的數據結構&#xff0c;而是基于 String 類型數據結構實現的一種存儲方式。由于 String 類型的最大上限是 512M&#xff0c;換算成 bit 位就是 2^32 個&#xff0c;這決定了 BitMap 可操作的最大范圍。Bit…