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

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

定時器作用及實現定時器數據結構選取介紹1

jf_78858299 ? 來源:程序員不是碼農 ? 作者:程序員不是碼農 ? 2023-04-21 15:20 ? 次閱讀

本文主要介紹定時器作用,實現定時器數據結構選取,并詳細介紹了跳表,紅黑樹,時間輪實現定時器的思路和方法。

定時器作用

定時器在各種場景都需要用到,比如游戲的Buff實現,Redis中的過期任務,Linux中的定時任務等等。顧名思義,定時器的主要用途是執行定時任務。

定時器數據結構選取

定時器數據結構要求:

  • 需要快速找到到期任務,因此,應該具有有序性;
  • 其過期執行、插入(添加定時任務)和刪除(取消定時任務)的頻率比較高,三種操作效率必須保證

以下為各數據結構時間復雜度表現

有序鏈表:插入O(n),刪除O(1),過期expire執行O(1)

最小堆:插入O(logn),刪除O(logn),過期expire執行O(1)

紅黑樹:插入O(logn),刪除O(logn),過期expire執行O(logn)

哈希表+鏈表(時間輪):插入O(1),刪除O(1),過期expire平均執行O(1)(最壞為O(n)

不同開源框架定時器實現方式不一,如,libuv采用最小堆來實現,nginx采用紅黑樹實現,linux內核和skynet采用時間輪算法實現等等。

定時器接口封裝

作為定時器,需要封裝以下4類接口給用戶使用:

  • 創建定時器:init_timer
  • 添加定時任務:add_timer
  • 取消定時任務:cancel_timer
  • 執行到期任務:expire_timer

其中執行到期任務有兩種工作方式:

  1. 輪詢: 每隔一個時間片去查找哪些任務到期
  2. 睡眠/喚醒:不停查找deadline最近任務,到期執行,否則sleep;sleep期間,任務有改變,線程會被喚醒

接下來將介紹分別用跳表、紅黑樹、時間輪來實現定時器。

跳表實現定時器

跳表簡介

跳表是一種動態的數據結構,采用空間換時間的思想,在有序鏈表基礎上加入多級索引,通過索引進行二分快速查找,支持快速刪除、插入和查找操作(平均時間復雜度為O(logN),最壞為O(N)),效率可與平衡樹媲美,實現比其簡單。

下面通過一張圖來簡單說明跳表操作。跳表的最底層即為基本的有序鏈表,存儲所有的數據,可理解為數據層;往上則為索引層,理想狀態下,上一層為下一層節點數的一半。比如,要查找下圖的數據為11的節點,從begin''出發,向右走,如果下一個節點大于11則往下走,直到找到目標節點。可見,跳表要比原始鏈表少比較一些節點,但前提是需要花更多空間存儲索引節點。

圖片

image-20210323182236910

跳表實現定時器

  • 跳表查找,插入,刪除(任意節點、頭節點)的時間復雜度大概率趨向于O(logn)
  • 過期任務查找,只需要跟第一個節點比較,因其第一個節點即為最小節點

學會吸取開源框架中優秀數據結構和代碼思想,直接采用redis中跳表結構的實現,取出所需部分,用于實現定時器。如下:

跳表數據結構

跳表節點與跳表結構

/*skiplist.h*/
#define ZSKIPLIST_MAXLEVEL 32
#define ZSKIPPLIST 0.25

typedef struct zskiplistNode zskiplistNode;
typedef void (*handler_pt) (zskiplistNode * node);
// 跳表節點
struct zskiplistNode {
  unsigned long score;  /*用于排序的值*/
  handler_pt handler;  /*處理函數*/
  struct zskiplistLevel {
    struct zskiplistNode **forward;
  }level[];
};
// 跳表結構
typedef struct zskiplist {
  struct zskiplistNode * header;
  int length;
  int level;  /*跳表層數*/
}zskiplist;

跳表接口申明

具體接口實現細節請移步redis源碼。

/*skiplist.h*/
/*創建跳表,初始化*/
zskiplist *zslCreate(void);
/*刪除跳,表釋放資源*/
void zslFree(zskiplist *zsl);
/*插入節點*/
zskiplistNode *zslInsert(zskiplist *zsl, unsigned long score, handler_pt func);
/*刪除頭節點*/
void zsklDeleteHead(zskiplist *zsl);
/*刪除任意節點*/
void zslDelete(zskiplist *zsl, zskplistNode *zn);
/*打印,調試*/
void zslPrint(zskiplist *zsl);

定時器接口實現

主要介紹四個接口實現:初始化定時器,添加定時任務,刪除/取消定時任務,處理定時任務

// test_user.c  封裝給用戶使用的接口
static uint32_t
current_time() {
 uint32_t t;
    struct timespec ti;
    clock_getttime(CLOCK_MONOTONIC, &ti);
    t = (uint32_t)ti.tv_sec * 1000;
    t += ti.tv_sec / 1000000;
}
zskiplist *init_timer() {
    // 初始化定時器
    return zslCreate();
}
zskiplistNode *add_timer(zskiplist *zsl, uint32_t msec, handler_pt func) {
    // 添加定時任務
    msec += current_time();
    return zslInsert(zsl, msec, func);
}
void cancel_timer(zskiplist *zsl, zskiplistNode *zn) {
    // 刪除/取消定時任務
    zslDelete(zsl, zn);
}
void expire_timer(zskiplist *zsl){
    // 處理定時任務
    zskiplistNode *x;
    uint32_t now = current_time();
    for (;;) {
        x = zslMin(zsl);  // 最近節點
        if (!x) break;
        if (x->score > now)  break;  // 時間未到
        x->handler(x);  // 執行相關定時任務
        zslDeleteHead(zsl);  // 執行完刪除
    }
}

紅黑樹實現定時器

紅黑樹

紅黑樹是一種自平衡的二叉查找樹,即,插入和刪除操作如果破壞樹的平衡時,需要重新調整達到平衡狀態。因此,是一種比較難的數據結構。

紅黑樹五條性質

  • 每個節點要么是紅色,要么是黑色
  • 根節點是黑色
  • 每個葉子結點是黑色
  • 每個紅節點的兩個子節點一定是黑色
  • 任意一節點到每個葉子節點的路徑都含相同數目的黑結點

弄懂紅黑樹如何調整樹的平衡,保證滿足這5條性質,是比較麻煩,需要耐心的去推導一遍,此處不展開。

紅黑樹實現定時器

AVL 樹平衡要求太高,維護平衡操作過多,較復雜;紅黑樹只需維護一個黑高度,效率較高

紅黑樹查找,刪除,添加時間復雜度為:O(log(n))

吸取開源框架中優秀數據結構和代碼思想,選用nginx中的紅黑樹結構

紅黑樹數據結構

紅黑樹節點與紅黑樹

// rbtree.h  紅黑樹數據結構以及相關接口,具體接口實現同上
#ifndef _NGX_RBTREE_H_INCLUDE_
#define _NGX_RBTREE_H_INCLUDE_

typedef unsigned int ngx_rbtree_key_t;
typedef unsigned int ngx_uint_t;
typedef int ngx_rbtree_key_int_t;

// 紅黑樹節點
typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;
struct ngx_rbtree_node_s {
    ngx_rbtree_key_t key;
    ngx_rbtree_node_t *left;
    ngx_rbtree_node_t *right;
    ngx_rbtree_node_t *parent;
    u_char    color;  // 節點顏色
    u_char    data;  // 節點數據
};
// 插入函數指針
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
// 紅黑樹
typedef struct ngx_rbtree_s ngx_rbtree_t;
struct ngx_rbtree_s {
    ngx_rbtree_node_t  *root;
    ngx_rbtree_node_t  *sentinel;
    ngx_rbtree_insert_pt insert;
};

紅黑樹接口聲明

// 紅黑樹初始化
#define ngx_rbtree_init(tree, s, i)       \\
 ngx_rbtree_sentinel_init(s);      \\
 (tree)->root = s;        \\
 (tree)->sentinel = s;       \\
 (tree)->insert = i;        
// 插入操作
void ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);
// 刪除操作
void ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);
// 插入value
void ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node,
                            ngx_rbtree_node_t *sentinel);
// 插入timer
void ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *root,
                                  ngx_rbtree_node_t *node,
                                  ngx_rbtree_node_t *sentinel);
// 獲取下一個節點
ngx_rbtree_node_t *ngx_rbtree_next(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);
#define ngx_rbt_red(node)    ((node)->color = 1)
#define ngx_rbt_black(node)    ((node)->color = 0)
#define ngx_rbt_is_red(node)   ((node)->color)
#define ngx_rbt_is_black(node)   (!ngx_rbt_is_red(node))
#define ngx_rbt_copy_color(n1, n2)  (n1->color = n2->color)
#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)
// 找到最小值,一直往左走即可
static inline ngx_rbtree_node_t *
ngx_rbtree_min(ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
    while (node->left != sentinel){
        node = node->left;
    }
    return node;
}

定時器接口實現

// test_user.c  封裝給用戶使用的接口
ngx_rbtree_t     timer;
static ngx_rbtree_node_t   sentinel;
typedef struct timer_entry_s timer_entry_t;
typedef void (*timer_handler_pt)(timer_entry_t *ev);

struct timer_entry_s {
   ngx_rbtree_node_t timer;
    timer_handler_pt  handler;
};
// 初始化
int init_timer() {
    ngx_rbtree_init(&timer, &sentinel, ngx_rbtree_insert_timer_value);
    return 0;
}
// 添加定時任務
void add_timer(timer_entry_t *te, uint32_t msec) {
    msec += current_time();
    te->timer.key = msec;
    ngx_rbtree_insert(&timer, &te->timer);
}
// 取消定時
void cancel_timer(timer_entry_t *te) {
    ngx_rbtree_delete(&timer, &te->timer);
}
// 執行到期任務
void expire_timer() {
    timer_entry_t *te;
    ngx_rbtree_node_t *sentinel, *root, *node;
    sentinel = timer.sentinel;
    uint32_t now = current_time();
    for(;;){
        root = timer.root;
        if (root == sentinel) break;
        if (node->key > now) break;
        te = (timer_entry_t *) ((char *) node - offsetof(timer_entry_t, timer));
        te->handler(te);
        ngx_rbtree_delete(&timer, &te->timer);
        free(te);
    }
}

以上,為紅黑樹和跳表實現的定時器,多線程環境下加鎖粒度比較大,高并發場景下效率不高,而時間輪適合高并發場景,如下。

時間輪實現定時器

時間輪

可以用于高效的執行大量定時任務,如下為分層時間輪示意圖:

圖片

timewheel

時間輪可參考時鐘進行理解,秒針(Seconds wheel)轉一圈,則分針(Minutes wheel)走一格,分針(Minutes wheel)轉一圈,則時針(Hours wheel)走一格。隨著,時間的流逝,任務不斷從上層流下下一層,最終到達秒針輪上,當秒針走到時執行。

如上所示,時間輪大小為8格,秒針1s轉動一格,其每一格所指向的鏈表保存著待執行任務。比如,如果當前指針指向1,要添加一個3s后執行的任務,由于1+3=4,即在第4格的鏈表中添加一個任務節點即可。如果要添加一個10s后執行的任務,10+1=11,超過了秒針輪范圍,因此需要對8取模11 % 8 = 3,即,會把這個任務放到分針輪上3對應的鏈表上,之后再從分針輪把任務丟到秒針輪上進行處理。也即,**秒針輪(Seconds wheel)**即保存著最近將要執行的任務,隨著時間的流逝,任務會慢慢的從上層流到秒針輪中進行執行。

優點:加鎖粒度較小,只需要加一個格子即可,一個格子對應一串鏈表;適合高并發場景

缺點:不好刪除

如何解決時間輪定時任務刪除?

  1. 通過引用計數來解決
  2. 交由業務層處理,將刪除標記設為true , 在函數回調中根據這個標記判斷是否需要處理

這里介紹兩種定時器實現方案,一種是簡單實現方案,另一種是skynet較為復雜的實現。

時間輪實現定時器

簡單時間輪實現方案

功能場景:由心跳包進行超時連接檢測,10s未收到則斷開連接

一般做法:map每秒輪詢這個結構,檢測所有連接是否超時,收到心跳包,記錄時間戳

缺點:效率很差,每次需要檢測所有連接,時間復雜度為O(n)

優化:分治大法,只需檢測快過期的連接, 采用hash數組+鏈表形式,數組大小設置成16 :[0] + [1] + [2] + ... + [15] ,相同過期時間的放入一個數組,因此,每次只需檢測最近過期的數組即可,不需要遍歷所有。

數據結構定義

以下為定時器節點,增加引用計數ref, 只有當ref為0時刪除連接。

class CTimerNode {
public:
    CTimerNode(int fd) : id(fd), ref(0) {}
    void Offline() {this->ref = 0};
    bool tryKill() {
        if (this->ref == 0) return true;
        DecRef();
        if (this->ref == 0){
            return true;
        }
        return false;
    }
    void IncRef() {this->ref++;}
protected:
    void DecRef() {this->ref--;}
private:
    int ref;
    int id;
}
// 時間輪數組大小16, (x對16取余)==(x&1111) 落到0-15之間,即落到對應的數組
const int TW_SIZE = 16;
const in EXPIRE = 10; // 過期間隔
const int TW_MASK = TW_SIZE - 1;  // 掩碼, 用于對16取余
static size_t iReadTick = 0;  // 滴答時鐘
typedef list
定時器接口
// 添加定時
void AddTimeOut(TimerWheel &tw, CTimerNode *p) {
    if (p) {
        p->IncRef();
        // 找到iRealTick對應數組的idx(槽位)
        TimeList &le = tw[(iRealTick+EXPIRE) & TW_MASK];
        le.push_back(p);  // 把時間節點加入list中
    }
}
// 延時調用
void AddTimeOutDelay(TimeWheel &tw, CTimerNode *p, size_t delay) {
    if (p) {
        p->IncRef();
        TimeList &le = tw[(iRealTick + EXPIRE + delay) & TW_MASK];
        le.push_back(p);
    }
}
// 時間輪移動
void TimerShift(TimeWheel &tw) {
    size_t tick = iRealTick;
    iRealTick++;
    TimeList &le = tw[tick & TW_MASK];
    TimeListIter iter = le.begin();
    for (; iter != le.end(); iter++) {
        CTimerNode *p = *iter;
        if (p && p->trySkill()){
            delete p;
        }
    }
    le.clear();
}
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11378

    瀏覽量

    211339
  • 定時器
    +關注

    關注

    23

    文章

    3259

    瀏覽量

    115909
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    40328
收藏 人收藏

    評論

    相關推薦

    定時器/計數基礎

    15-1.實現定時的方法15-2.定時器/計數結構和工作原理 15-3.
    發表于 03-23 12:17 ?48次下載

    555定時器

    555定時器555定時器555定時器555定時器555定時器555定時器555
    發表于 11-10 17:25 ?52次下載

    定時器介紹

    同時用兩個定時器控制蜂鳴器發聲, 定時器0控制頻率,定時器1控制同個 頻率持續的時間,間隔2s依次輸出 1,10,50100,2004
    發表于 02-23 15:56 ?20次下載

    詳細介紹定時器定時器中斷

    在測量控制系統中,常常需要實時時鐘,以實現定時控制、定時測量或定時中斷等。也常需要計數實現
    的頭像 發表于 02-09 14:00 ?1.8w次閱讀
    詳細<b class='flag-5'>介紹</b><b class='flag-5'>定時器</b>和<b class='flag-5'>定時器</b>中斷

    STC51定時器定時器中斷

    1.定義定時器介紹: 51單片機的定時器屬于單片機的內部資源,其電路的連接和運轉均在單片機內部完成。2.作用
    發表于 11-22 14:51 ?5次下載
    STC51<b class='flag-5'>定時器</b>與<b class='flag-5'>定時器</b>中斷

    stm32—定時器配置

    目錄定時器組成通用寄存通用寄存簡介:通用定時器 TIMx (TIM2-TIM5 )的功能:通用定時器
    發表于 11-22 17:51 ?11次下載
    stm32—<b class='flag-5'>定時器</b>配置

    STM32基于cubeMX實現定時器點燈

    Cortex M3內核當中的定時器,它并不屬于芯片廠商的外設,也就是說使用ARM內核的不同廠商,都擁有基本結構相同的系統定時器。主要目的是給RTOS提供時鐘節拍做時間基準?;?b class='flag-5'>定時器
    發表于 11-23 18:21 ?19次下載
    STM32基于cubeMX<b class='flag-5'>實現</b><b class='flag-5'>定時器</b>點燈

    STM32定時器-基本定時器

    目錄定時器分類基本定時器功能框圖講解基本定時器功能時鐘源計數時鐘計數自動重裝載寄存
    發表于 11-23 18:21 ?32次下載
    STM32<b class='flag-5'>定時器</b>-基本<b class='flag-5'>定時器</b>

    基于硬件定時器的軟件定時器

    出現使用軟件定時器的情況,但是講定時器需要從硬件定時器開始講,軟件定時器是在其基礎之上延伸出來的。硬件定時器
    發表于 11-25 09:51 ?8次下載
    基于硬件<b class='flag-5'>定時器</b>的軟件<b class='flag-5'>定時器</b>

    STM32——高級定時器、通用定時器、基本定時器的區別

    STM32——高級定時器、通用定時器、基本定時器的區別
    發表于 11-26 15:21 ?110次下載
    STM32——高級<b class='flag-5'>定時器</b>、通用<b class='flag-5'>定時器</b>、基本<b class='flag-5'>定時器</b>的區別

    SysTick 定時器

    的SysTick定時器實現延時,可以不占用系統定時器,節約資源。由于SysTick是在CPU核內部實現的,跟MCU外設無關,因此它的代碼可以在不同廠家之間移植。本 章 將 使用系統滴
    發表于 12-05 14:51 ?9次下載
    SysTick <b class='flag-5'>定時器</b>

    定時器作用實現定時器數據結構選取介紹2

    定時器在各種場景都需要用到,比如游戲的Buff實現,Redis中的過期任務,Linux中的定時任務等等。顧名思義,定時器的主要用途是執行定時
    的頭像 發表于 04-21 15:20 ?1274次閱讀
    <b class='flag-5'>定時器</b><b class='flag-5'>作用</b>及<b class='flag-5'>實現</b><b class='flag-5'>定時器</b><b class='flag-5'>數據結構</b><b class='flag-5'>選取</b><b class='flag-5'>介紹</b>2

    什么是軟件定時器?軟件定時器實現原理

    軟件定時器是用程序模擬出來的定時器,可以由一個硬件定時器模擬出成千上萬個軟件定時器,這樣程序在需要使用較多定時器的時候就不會受限于硬件資源的
    的頭像 發表于 05-23 17:05 ?3045次閱讀

    定時器設計實現

    返回ITimer類型的共享指針。其中ITimer類中定義了start和stop方法,用于啟動或停止當前定時器。 TimerManager還有一個內部類TimerMessageQueue用于實現
    的頭像 發表于 11-08 16:50 ?732次閱讀

    定時器實現數據結構選擇

    在后端的開發中,定時器有很廣泛的應用。 比如: 心跳檢測 倒計時 游戲開發的技能冷卻 redis的鍵值的有效期等等,都會使用到定時器。 定時器實現
    的頭像 發表于 11-13 14:22 ?618次閱讀
    <b class='flag-5'>定時器</b>的<b class='flag-5'>實現</b><b class='flag-5'>數據結構</b>選擇
    主站蜘蛛池模板: 黑人操日本女人 | yellow在线观看免费高清的日本 | 欧美又粗又长又大AAAA片 | 花蝴蝶在线高清视频观看免费播放 | 恋孩癖网站大全在线观看 | 欲香欲色天天综合和网 | 色偷拍自怕亚洲在线 | 男宿舍里的呻吟h | 久久成人a毛片免费观看网站 | 日日噜噜夜夜爽爽 | 伊人久久影视 | 久久视频这里只精品99re8久 | WWW国产亚洲精品久久 | 免费韩伦影院在线观看 | 黑丝美女被人操 | 日韩AV无码一区二区三区不卡毛片 | 天天啪免费视频在线看 | 最近免费中文字幕MV在线视频3 | 免费可以看污动画软件 | 精品人妻一区二区三区视频53 | 免费在线看视频 | 大桥未久在线看 | 无颜之月全集免费观看 | 亚洲精品成人在线 | 亚洲第一综合天堂另类专 | 无码AV熟妇素人内射V在线 | 妞干网手机免费视频 | 欧美亚洲国内日韩自拍视频 | 小柔的性放荡羞辱日记 | 久久久久综合 | 国内精品久久久久久久999下 | 九九热视频在线观看 | 国产高清免费观看 | caoporon超碰在线视频 | 青草精品国产福利在线视频 | 甜性涩爱在线播放 | 国产色无码精品视频国产 | xart欧美一区在线播放 | 亚洲午夜久久久精品影院 | 99久久免费只有精品 | 学生小泬无遮挡女HD |