高性能定时器2——红黑树实现

本文详细介绍了如何使用红黑树作为定时器容器,有效管理服务器的定时事件,确保在预定时间触发且不影响核心逻辑。通过R-BTree的自平衡特性,提升服务器性能的关键技术。

在网络程序中我们通常要处理三种事件,网络I/O事件、信号以及定时事件,我们可以使用I/O复用系统调用(select、poll、epoll)将这三类事件进行统一处理。我们通常使用定时器来检测一个客户端的活动状态,服务器程序通常管理着众多定时事件,因此有效地组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器的性能有着至关重要的影响。为此我们需要将每个定时事件分别封装为定时器,并使用某种容器类数据结构,比如:链表、排序链表、最小堆、红黑树以及时间轮等,将所有定时器串联起来,以实现对定时事件的统一管理。此处所说的定时器,确切的说应该是定时容器,定时器容器是容器类数据结构;定时器则是容器内容纳的一个个对象,它是对定时事件的封装,定时容器是用来管理定时器的。

在本文中将主要介绍使用红黑树来实现的定时容器。

1、R-B Tree简介

​ R-B Tree,是Red Black Tree的缩写,也叫做红黑树。红黑树是一种带有颜色属性(红色或黑色)的自平衡的二叉查找树。它查找、插入和删除的时间复杂度为log(N),N为树中元素的数目。

红黑树的特征:

​ 1)节点是红色或黑色;

​ 2)根节点是黑色;

​ 3)所有叶子节点都是黑色(叶子节点是NIL节点,也叫“哨兵”);

​ 4)每个红色节点的两个子节点都是黑色(每个叶子节点到根节点的所有路径上不能有两个连续的红色节点);

​ 5)从任一节点到其每个叶子节点的所有简单路径都包含相同数目的黑色节点。

红黑树会通过旋转以及变色来保持树的平衡。

如下图为一棵红黑树:

​ 最底部的黑色节点为哨兵节点。

在这里插入图片描述

在这只对红黑树做一个简单介绍,红黑树的实现较为复杂,有对红黑树的实现感兴趣的,可以自己研究,nginx的红黑树很值得一读,nginx红黑树的代码在src/core/中,为ngx_rbtree.h和ngx_rbtree.c。红黑树使用还是很广泛的,比如c++ STL中的map以及set底层的实现使用的都是红黑树。

2、Nginx红黑树相关接口介绍

nginx红黑树中节点的定义如下:

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
   
   
    ngx_rbtree_key_t       key;                 // 键值,红黑树排序的依据 uint类型
    ngx_rbtree_node_t     *left;                // 左儿子
    ngx_rbtree_node_t     *right;               // 右儿子
    ngx_rbtree_node_t     *parent;              // 父节点
    u_char                 color;               // 节点的颜色:0表示黑色,1表示红色
    u_char                 data;                // 节点数据
};

nginx红黑树中的节点数据只有一个字节,如果我们要插入自己的数据,就需要在自定义结构体或类中添加一个ngx_rbtree_node_t类型的字段,将该字段作为结构体或类的第一个字段,便于类型的强制转换。例如:

struct my_rbtree_node
{
   
   
    ngx_rbtree_node_t node;    // 红黑树节点
   	UserData data;             // 用户自己的数据
}

ngx_rbtree_t结构为承载红黑树的结构体,其定义如下:

struct ngx_rbtree_s {
   
   
    ngx_rbtree_node_t     *root;                // 根节点,根节点也是数据元素
    ngx_rbtree_node_t     *sentinel;            // 哨兵节点
    ngx_rbtree_insert_pt   insert;              // 表示红黑树添加元素的函数指针
};

最后一个insert字段是一个函数指针,nginx的代码中已经实现了两种红黑树插入的方法,分别为ngx_rbtree_insert_value和ngx_rbtree_insert_timer_value;

使用步骤:

​ 1)初始化红黑树,使用ngx_rbtree_init来初始红黑树

#define ngx_rbtree_init(tree, s, i)  \   // tree为ngx_rbtree_t类型的指针,s为哨兵节点指针,i为
    ngx_rbtree_sentinel_init(s);     \   // 插入节点的回调函数,可以使用ngx_rbtree_insert_value
    (tree)->root = s;                \   // 和ngx_rbtree_insert_timer_value,也可以自定义
    (tree)->sentinel = s;            \
    (tree)->insert = i

​ 2) 执行插入、删除等操作

// 向红黑树中添加节点
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);

3、红黑树定时容器的实现

​ 定时器的实现可以使用多种数据结构,比如:最小堆,libevent中的定时器使用的就是最小堆;红黑树,nginx的定时器使用红黑树来实现;以及时间轮等。下面介绍的定时容器的实现使用nginx中的红黑树实现代码。

该定时器容器的思路是:将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔,这样,一旦心搏函数tick被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。然后,再从剩余的定时器中找到超时时间最小的一个,并将这段最小时间设置为下一次心搏间隔,如此反复,就实现了较为精确的定时。

红黑树定时容器的几个接口介绍:

​ 1) tick :在tick函数中循环查找超时值最小的定时器,并调用其回调函数,直到找到的定时器的没有超时,就结束循环。

​ 2)addTimer::向容器中添加一个定时器,并返回定时器的指针。

​ 3)delTimer::根据传入的定时器指针删除容器中的一个定时器,并且销毁资源。

​ 4)resetTimer: 重置一个定时器。

​ 5)getMinExpire:获取容器中超时值最小的绝对时间;

代码如下:

timer_common.cpp

#ifndef _LIB_SRC_TIMER_COMMON_H
#define _LIB_SRC_TIMER_COMMON_H

#include <stdio.h>
#include <sys/time.h>

// 获取时间戳 单位:毫秒
time_t getMSec()
{
   
   
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 定时器数据结构的定义
template <typename _User_Data>
class Timer
{
   
   
public:
    Timer() : _user_data(nullptr), _cb_func(nullptr) {
   
   };
    Timer(int msec) : _user_data(nullptr), _cb_func(nullptr)
    {
   
   
        this->_expire = getMSec() + msec;
    }

    ~Timer()
    {
   
   

    }

    void setTimeout(time_t timeout)
    {
   
   
        this->_expire = getMSec() + timeout;
    }

    time_t getExpire()
    {
   
   
        return _expire;
    }

    void setUserData(_User_Data *userData)
    {
   
   
        this->_user_data = userData;
    }

    void handleTimeOut()
    {
   
   
        if(_cb_func)
        {
   
   
            _cb_func(_user_data);
        }
        
    }

    using TimeOutCbFunc = void (*)(_User_Data *);
    void setCallBack(TimeOutCbFunc callBack)
    {
   
   
        this->_cb_func = callBack;
    }

private:
    time_t _expire;                    // 定时器生效的绝对时间            
    _User_Data *_user_data;            // 用户数据
    TimeOutCbFunc _cb_func;           // 超时时的回调函数
};


// 虚基类ITimerContainer
template <typename _UData>
class ITimerContainer 
{
   
   
public:
    ITimerContainer() = default;
    virtual ~ITimerContainer() = default;

public:
    virtual void tick() = 0;               
    virtual Timer<_UData> *addTimer(time_t timeout) = 0;
    virtual void delTimer(Timer<_UData> *timer) = 0;
    virtual void resetTimer(Timer<_UData> *timer, time_t timeout) = 0;
    virtual int getMinExpire() = 0;
};

#endif

nginx红黑树代码:

rbtree.h:

#ifndef _NGX_RBTREE_H_INCLUDED_
#define _NGX_RBTREE_H_INCLUDED_

#include <stdint.h>      
#include <stdio.h>

#define ngx_inline inline

typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_t;

typedef ngx_uint_t  ngx_rbtree_key_t;
typedef ngx_int_t   ngx_rbtree_key_int_t;

typedef unsigned char u_char;

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

// 红黑树的节点  
struct ngx_rbtree_node_s {
   
   
    ngx_rbtree_key_t       key;                 // 键值,红黑树排序的依据 uint类型
    ngx_rbtree_node_t     *left;                // 左儿子
    ngx_rbtree_node_t     *right;               // 右儿子
    ngx_rbtree_node_t     *parent;              // 父节点
    u_char                 color;               // 节点的颜色:0表示黑色,1表示红色
    u_char                 data;                // 节点数据
};


typedef struct ngx_rbtree_s  ngx_rbtree_t;

// 定义插入节点的函数指针
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

// 红黑树结构
struct ngx_rbtree_s {
   
   
    ngx_rbtree_node_t     *root;                // 根节点,根节点也是数据元素
    ngx_rbtree_node_t     *sentinel;            // 哨兵节点
    ngx_rbtree_insert_pt   insert;              // 表示红黑树添加元素的函数指针,它决定在添加新节点时的行为究竟是替换还是新增
};


/* 
    初始化红黑树, tree为红黑树容器的指针, s为哨兵节点的指针, i为ngx_rbtree_insert_pt类型的节点添加方法
    Nginx为红黑树已经实现的三种添加节点的方法:
        ngx_rbtree_insert_value
        ngx_rbtree_insert_timer_value
        ngx_str_rbtree_insert_value
    在初始化红黑树时,需要先分配好保存红黑树的ngx_rbtree_t结构体,以及ngx_rbtree_node_t类型的哨兵节点,
    并选择或者自定义ngx_rbtree_insert_pt类型的节点添加函数。
*/
#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);

void ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel);
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);

// 查找节点
ngx_rbtree_node_t *ngx_rbtree_find(ngx_rbtree_t *tree, ngx_rbtree_node_t *node, 
    ngx_rbtree_node_t *sentinel);

#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)


/* a sentinel must be black */       /* 哨兵节点必须是黑色的 */
#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)     // 初始化哨兵节点,也就是将哨兵节点的颜色置为黑色


// 获取红黑树中key最小的节点
static ngx_inline ngx_rbtree_node_t *
ngx_rbtree_min
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值