【仿muduo庫實現并發服務器】實現時間輪定時器

實現時間輪定時器

  • 1.時間輪定時器原理
  • 2.項目中實現目的
  • 3.實現功能
    • 3.1構造定時任務類
    • 3.2構造時間輪定時器
      • 每秒鐘往后移動
      • 添加定時任務
      • 刷新定時任務
      • 取消定時任務
  • 4.完整代碼

1.時間輪定時器原理

時間輪定時器的原理類似于時鐘,比如現在12點,定一個3點的鬧鐘,那么過了三小時后,鬧鐘就會響起。
我們就可以定義一個時間數組,并有一個指針tick,指向數組的起始位置,每個元素下位置代表著具體時間,比如第二個元素位置為第一秒(起始位置為0秒),第三個元素位置表示第二秒,…第60個元素位置代表60秒。而tick指針每秒鐘都向后移動一步,代表過了1秒。而走到哪里,就表示 哪里的任務該被執行了。

當我想要實現一個5秒后要執行的定時器,只需要將該定時器放入數組tick+5的位置去。tick指針每秒鐘會向后移動一步,當5秒后,會走到對應的位置,這時去執行對應的定時任務即可。
在這里插入圖片描述

不過同一時間可能會有多個定時任務需要執行,所以可以定一個二維的數組。
每個時間可以存放多個定時任務。
在這里插入圖片描述

不過這里時間到了需要主動去執行對應的任務,我們有更好的方法可以自動執行對應的任務。
【類的析構】:
我們利用類的析構自動會執行的特性,所以我們可以將定時任務弄成一個類,并將任務放在析構函數中,當對象銷毀時,就會自動的執行析構函數里面的定時任務。

2.項目中實現目的

在該項目中需要定時器的主要目的是用來管理每個連接的生命周期的,因為有的連接是惡意的,長時間連接啥也不干,所以為了避免這種情況,規定當一個連接創建時,超過一定時間沒有動靜的,就主動釋放掉該連接。這時候就需要設置一個固定時間后的定時銷毀任務。
比如規定時間為30s,當一個連接超過30沒有發送數據或接收數據就需要釋放掉。

目的:希望非活躍的連接在N秒后被釋放掉。

【刷新時長】
不過當一個連接在創建后(定時任務放入30s位置上)第10秒時發送了數據,該連接的存活時間就需要重新更新,在第40秒后再釋放。也就是在40s的位置上再把該定時任務插入進去。

需要在一個連接有IO事件產生的時候,延遲定時任務的執行

如何實現呢?
【shared_ptr+weak_ptr】
shared_ptr中有一個計數器,當計數器為0時資源才會真正釋放。
只要讓時間輪定時器里存儲的是指向定時任務對象的智能指針shared_ptr,而不是定時器對象,這樣就可以把要更新后的定時任務再插入進去。
這時候shared_ptr的計數器就為2,當tick走過30秒時對應的銷毀任務不會執行,只會將計數器–變為1,而走到40s時,對應的銷毀任務才會執行,因為這時候shared_ptr的計數器就為0了。

基于這個思想,我們可以使用shared_ptr來管理定時器任務對象

不過要主要需要使用weak_ptr來保存插入到時間輪里的定時任務對象信息。因為weak_ptr是弱引用,它不會增加shared_ptr的計數,還可以獲取對應的shared_ptr對象。

  1. 首先第一個將任務封裝起來,讓這個任務呢在一個對象析構的
    時候再去執行它。
    2.而這個對象呢,使用shared_ptr來管理起來,添加定時任務只是添加了我們的一個shared_ptr的一個ptr對象。
    3.當要延遲一個任務的執行只需要針對這個任務呢?再去重新生成shared_ptr,添加到時間輪里邊。
    4.該任務的計數器,就變就會加1,當前面的shared_ptr就算釋放的也不會去釋放所管理的對象那么,只有到后邊的這個shared_ptr釋放的時候計數為O了,才會去釋放所管理的定時器任務。
    在這里插入圖片描述

3.實現功能

時間輪定時器的主要功能有:添加定時任務;刷新定時任務;取消定時任務;

3.1構造定時任務類

1.將定時任務封裝到一個類中,每個定時任務都有自己的的標識id,用它可以在時間輪中找到對應的定時器任務對象。
2.將定時任務的函數放在該類的析構函數中,當對象銷毀時自動執行,要定時的任務由由用戶指定所以通過回調函數_task_cb設置進去。
3.每個定時任務都有自己的超時時間timeout,當超過該時間就去執行該任務。
4.因為時間輪定時器中還需要保存每個定時器對象的weak_ptr,用來刷新定時任務,使用unordered_map來管理。通過定時器任務id找到對應的定時器weak_ptr對象。而當定時器對象銷毀時,還需要將該對象的weak_ptr信息從map表中移除。這個操作是需要在時間輪定時器中實現的,所以是需要使用回調函數_release_cb,在時間輪定時器中設置進去。
5.取消定時任務就是不執行析構函數中的回調函數即可。通過一個布爾值設置。

#include <iostream>
#include <vector>
#include <unordered_map>
#include <memory>
#include <stdint.h>
#include <functional>
#include <unistd.h>
using TaskFunc= std::function<void()>;
using ReleaseFunc=std::function<void()>;
class TimerTask
{
public://構造TimerTask(uint64_t id,uint32_t timeout,TaskFunc &cb):_id(id),_timeout(timeout),_canceled(false),_task_cb(cb){}//析構,當對象釋放時執行定時任務,并且從timerwheel中移除該定時任務的信息~TimerTask(){if(!_canceled){_task_cb();_release_cb();}}//獲取該定時器的超時時間uint32_t GetTimeout() {return _timeout;}void canceled() { _canceled = true; }/*取消定時器任務*/ void ReleaseTask(ReleaseFunc cb){_release_cb=cb;}  
private:uint64_t _id; //標識一個定時器對象uint32_t _timeout; //定時器的超時時間TaskFunc _task_cb; //要執行的定時任務bool _canceled;//定時任務默認是啟動的,false為啟動,true為終止定時器ReleaseFunc _release_cb;//釋放時要從timerwheel中移除該定時器信息};

3.2構造時間輪定時器

1.時間輪定時器我們通過vector來模擬二維數組。
2.而時間輪定時器中存儲的是shared_ptr對象(指向定時器任務對象的智能指針)
3.時間輪定時器需要有一個tick,就是一個滴答指針,每秒鐘向后移動一步,代表過了一秒。
4.時間輪定時器中還需呀一個哈希表管理著所有插入進來的定時任務對象的weak_ptr對象。通過定時任務的id來映射找到(當添加定時任務時,就會將id和對應的weak_ptr對象插入進去)

每秒鐘往后移動

定時器啟動后,tick指針每秒鐘都要往后移動一步。tick走到哪,就代表對應位置的任務要被執行,執行的原理就是將對應位置管理資源的shared_ptr全部清除,那么shared_ptr銷毀后—>定時器對象銷毀---->執行析構函數中的任務。

   void RunTime(){_tick=(_tick+1)%_capacity;_wheel[_tick].clear();//將當前位置上的所有任務都釋放掉,也就是都執行掉。}

添加定時任務

1.添加一個定時任務時,外部會給定這個定時器任務的id,超時時間和執行方法。
所以首先要根據這些構造一個shared_ptr對象。然后將釋放函數設置到定時任務中。
2.插入的位置是所在tick基礎上再向后移動timeout位置。
3.插入到時間輪里
4.將該定時任務信息以WeakPtr形式保存一份在map中。

 void SetRelease(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;_timers.erase(it);}//添加定時任務void AddTask(uint64_t id,uint32_t timeout,TaskFunc cb){//首先構建一個shared_ptr類型的定時器任務PtrTask pt(new TimerTask(id,timeout,cb));//將釋放函數內置進去pt->ReleaseTask(std::bind(&TimerWheel::SetRelease,this,id));int pos=(_tick+timeout)%_capacity;//插入到時間輪中_wheel[pos].push_back(pt);//再將該定時器任務保存一份信息在timers中_timers[id]=WeakTask(pt);}

刷新定時任務

當需要對定時任務進行延遲時,只需要根據該定時任務的id,去map表里找對應weak_ptr對象,并從weak_ptr對象中獲取對應的shared_ptr對象,然后再在tick的基礎上加上該定時器的超時時間,插入到時間輪里即可。

//刷新定時任務void RefreshTask(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;PtrTask pt=_timers[id].lock();//獲取weakptr保存的shared_ptruint32_t delay=pt->GetTimeout();int pos=(_tick+delay)%_capacity;_wheel[pos].push_back(pt);}

取消定時任務

要取消一個定時任務,只需要根據該定時任務的id到map表中找打它的weak_ptr對象,然后轉換為shared_ptr對象,執行對應的終止函數即可。

  void CancelTimer(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;PtrTask pt=_timers[id].lock();//獲取weakptr保存的shared_ptrpt->canceled();}

4.完整代碼


#include <iostream>
#include <vector>
#include <unordered_map>
#include <memory>
#include <stdint.h>
#include <functional>
#include <unistd.h>using TaskFunc= std::function<void()>;
using ReleaseFunc=std::function<void()>;
class TimerTask
{
public://構造TimerTask(uint64_t id,uint32_t timeout,TaskFunc &cb):_id(id),_timeout(timeout),_canceled(false),_task_cb(cb){}//析構,當對象釋放時執行定時任務,并且從timerwheel中移除該定時任務的信息~TimerTask(){if(!_canceled){_task_cb();_release_cb();}}//獲取該定時器的超時時間uint32_t GetTimeout() {return _timeout;}void canceled() { _canceled = true; }/*取消定時器任務*/ void ReleaseTask(ReleaseFunc cb){_release_cb=cb;}  private:uint64_t _id; //標識一個定時器對象uint32_t _timeout; //定時器的超時時間TaskFunc _task_cb; //要執行的定時任務bool _canceled;//定時任務默認是啟動的,false為啟動,true為終止定時器ReleaseFunc _release_cb;//釋放時要從timerwheel中移除該定時器信息};class TimerWheel
{using PtrTask=std::shared_ptr<TimerTask>;using WeakTask=std::weak_ptr<TimerTask>;
public://構造TimerWheel():_tick(0),_capacity(60),_wheel(_capacity){}void SetRelease(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;_timers.erase(it);}//添加定時任務void AddTask(uint64_t id,uint32_t timeout,TaskFunc cb){//首先構建一個shared_ptr類型的定時器任務PtrTask pt(new TimerTask(id,timeout,cb));//將釋放函數內置進去pt->ReleaseTask(std::bind(&TimerWheel::SetRelease,this,id));int pos=(_tick+timeout)%_capacity;//插入到時間輪中_wheel[pos].push_back(pt);//再將該定時器任務保存一份信息在timers中_timers[id]=WeakTask(pt);}//刷新定時任務void RefreshTask(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;PtrTask pt=_timers[id].lock();//獲取weakptr保存的shared_ptruint32_t delay=pt->GetTimeout();int pos=(_tick+delay)%_capacity;_wheel[pos].push_back(pt);}void CancelTimer(uint64_t id){auto it=_timers.find(id);if(it==_timers.end())return;PtrTask pt=_timers[id].lock();//獲取weakptr保存的shared_ptrpt->canceled();}void RunTime(){_tick=(_tick+1)%_capacity;_wheel[_tick].clear();//將當前位置上的所有任務都釋放掉,也就是都執行掉。}private:int _tick; //滴答指針,指向哪就執行對應的任務,也就是釋放該任務對象int _capacity; //定時器時間輪的容量大小std::vector<std::vector<PtrTask>> _wheel;//時間輪里存的是指向定時器任務對象的智能指針std::unordered_map<uint64_t,WeakTask> _timers;//存儲時間輪里的定時器信息
};class Test
{
public:Test(){std::cout<<"構造"<<std::endl;}~Test(){std::cout<<"析構"<<std::endl;}};//測試
void Delete(Test* t)
{delete t;
}
int main()
{Test* t=new Test();TimerWheel tw;tw.AddTask(888,5,std::bind(Delete,t));for(int i=0;i<5;i++){std::cout<<"---------------------"<<std::endl;tw.RefreshTask(888);tw.RunTime();sleep(1);}for(int i=0;i<5;i++){std::cout<<"---------------------"<<std::endl;tw.RunTime();sleep(1);}
}

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

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

相關文章

Windows10下搭建sftp服務器(附:詳細搭建過程、CMD連接測試、連接失敗問題分析解決等)

最終連接sftp效果 搭建sftp服務器 1、這里附上作者已找好的 freeSSHd安裝包 ,使用它進行搭建sftp服務器。 2、打開freeSSHd安裝包,進行安裝 (1)、選擇完全安裝 (2)、安裝完成后,對提示窗口選擇關閉 (3)、安裝完成后,提示是否安裝私有密鑰。我們選擇"是" (4)、安…

推薦幾個不錯的AI入門學習視頻

引言&#xff1a;昨天推薦了幾本AI入門書&#xff08;AI入門書&#xff09;&#xff0c;反響還不錯。今天&#xff0c;我再推薦幾個不錯的AI學習視頻&#xff0c;希望對大家有幫助。 網上關于AI的學習視頻特別多。有收費的&#xff0c;也有免費的。我今天只推薦免費的。 我們按…

點擊啟動「高效模式」:大騰智能 CAD 重構研發設計生產力

在制造業數字化轉型浪潮中&#xff0c;設計工具的革新正成為企業突破效率瓶頸的關鍵。傳統CAD軟件因本地硬件依賴、協作壁壘高筑、復雜場景響應遲緩等問題&#xff0c;長期困擾設計團隊。 大騰智能CAD依托華為云底座、自研幾何引擎及AI技術深度融合&#xff0c;為制造行業各細…

cursor如何開啟自動運行模式

在Cursor中&#xff0c;開啟自動運行模式即啟用“Yolo Mode”&#xff0c;具體操作如下&#xff1a; 按下Ctrl Shift J&#xff08;Windows/Linux&#xff09;或Cmd Shift J&#xff08;Mac&#xff09;打開Cursor設置。導航到“Features”&#xff08;功能&#xff09;選…

Windows10-ltsc-2019 使用 PowerShell 安裝安裝TranslucentTB教程(不通過微軟商店安裝)

Windows10-ltsc-2019 使用 PowerShell 安裝安裝TranslucentTB教程&#xff08;不通過微軟商店安裝&#xff09; 下載 v2020.4&#xff08;最后一個兼容 1809 的版本&#xff09;&#xff1a; TranslucentTB安裝包(下載不了上面有安裝包)安裝依賴項&#xff08;如未安裝&#x…

分布式拜占庭容錯算法——實現工作量證明(PoW)算法詳解

Java 實現工作量證明&#xff08;PoW&#xff09;算法詳解 一、PoW 核心原理 #mermaid-svg-AAj0Pvst1PVcVy5v {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AAj0Pvst1PVcVy5v .error-icon{fill:#552222;}#mermaid…

黑馬Java面試筆記之框架篇(Spring、SpringMvc、Springboot)

一. 單例bean Spring框架中的單例bean是線程安全的嗎&#xff1f; Spring框架中的bean是單例的&#xff0c;可以在注解Scope()進行設置 singleton&#xff1a;bean在每一個Spring IOC容器中只有一個實例。prototype&#xff1a;一個bean的定義可以有多個實例 總結 二. AOP AOP稱…

electron下載文件

const http require(http); const https require(https); const fs require(fs); const { URL } require(url); const path require(path);// 下載文件函數 function downloadFile(url, savePath) {return new Promise((resolve, reject) > {try {console.log(開始下載…

快速掌握 GO 之 RabbitMQ 結合 gin+gorm 案例

更多個人筆記見&#xff1a; &#xff08;注意點擊“繼續”&#xff0c;而不是“發現新項目”&#xff09; github個人筆記倉庫 https://github.com/ZHLOVEYY/IT_note gitee 個人筆記倉庫 https://gitee.com/harryhack/it_note 個人學習&#xff0c;學習過程中還會不斷補充&…

android FragmentManager 刪除所有Fragment 重建

在Android開發中&#xff0c;管理Fragment是一項常見任務&#xff0c;有時需要刪除所有Fragment并重新創建。這在某些場景下&#xff0c;例如用戶需要重置應用狀態或切換內容時&#xff0c;顯得尤為重要。本文將詳細介紹如何通過 FragmentManager刪除所有Fragment并重建。 一、…

ubuntu之開機自啟frpc

在 Ubuntu 系統中為 frpc 設置開機自啟&#xff08;以 frpc -c frpc.toml 命令為例&#xff09;&#xff0c;可以通過 systemd 服務實現。以下是詳細步驟&#xff1a; 創建 systemd 服務文件 sudo vim /etc/systemd/system/frpc.service 寫入以下內容&#xff08;根據你的路…

推薦一款PDF壓縮的工具

今天一位小伙伴找來&#xff0c;問我有沒有辦法將PDF變小的辦法。 詳細了解了一下使用場景&#xff1a; 小伙伴要在某系統上傳一個PDF文件&#xff0c;原文件是11.6MB&#xff0c;但是上傳時系統做了限制&#xff0c;只能上傳小于10MB的文件&#xff0c;如圖&#xff1a; 我聽…

JDK21深度解密 Day 11:云原生環境中的JDK21應用

【JDK21深度解密 Day 111】云原生環境中的JDK21應用 本文是《JDK21深度解密:從新特性到生產實踐的全棧指南》專欄的第11天內容,聚焦云原生環境中的JDK21應用。我們將深入探討如何在容器化、微服務、Serverless等云原生架構中充分發揮JDK21的技術優勢,提升Java應用的性能、穩…

Java-redis實現限時在線秒殺功能

1.使用redisson pom文件添加redisson <!--redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.4</version></dependency> 2.mysql數據庫表設…

QT- QML Layout+anchors 布局+錨點實現窗口部件權重比例分配

布局管理 簡單比較兩種界面管理錨點布局實現比例布局布局管理實現比例布局循環依賴問題簡談 在日常打螺絲中&#xff0c;我們偶爾會需要實現界面各組件能按比例放置&#xff0c;自適應各種分辨率的需求。我用錨點和布局都實現過相關界面&#xff0c;記錄下來兩種方式實現的差異…

Java項目OOM排查

排查思路 Java項目出現OOM&#xff08;Out Of Memory&#xff0c;內存溢出&#xff09;問題時&#xff0c;排查思路如下&#xff1a; 確認OOM類型&#xff1a; Java Heap Space&#xff1a;堆內存溢出&#xff0c;通常是對象創建過多或內存泄漏。PermGen Space&#xff1a;永久…

vue+threeJs 生成云狀特效屏幕

嗨&#xff0c;我是小路。今天主要和大家分享的主題是“vuethreeJs 生成云狀特效屏幕”。 動態云狀特效示例圖 二、實例代碼 <!--創建一個動態數字屏幕--> <template><div class"pageBox"><div class"leftBox" ref"lef…

ABAP設計模式之---“高內聚,低耦合(High Cohesion Low Coupling)”

“高內聚、低耦合”是面向對象編程中非常重要的設計原則&#xff0c;它有助于提高代碼的可維護性、擴展性和復用性。 1. 初衷&#xff1a;為什么會有這個原則&#xff1f; 在軟件開發中&#xff0c;隨著業務需求的復雜化&#xff0c;代碼難免會變得越來越龐大。如果開發者將一…

Registry和docker有什么關系?

當遇到多個服務器需要同時傳docker鏡像的時候&#xff0c;一個一個的傳效率會非常慢且壓力完全在發送方的網絡帶寬&#xff1b;可以參考git hub&#xff0c;通常我們會用git push將代碼傳到git hub&#xff0c;如果誰需要代碼用git pull就可以拉到自己的機器上&#xff0c;dock…

linux命令 systemctl 和 supervisord 區別及用法解讀

目錄 基礎與背景服務管理范圍配置文件和管理方式監控與日志依賴管理適用場景常用命令對照表實際應用場景舉例優缺點對比小結參考鏈接 1. 基礎與背景 systemctl 和 supervisord 都是用于管理和控制服務&#xff08;進程&#xff09;的工具&#xff0c;但它們在設計、使用場景和…