联系我们 - 广告服务 - 联系电话:
您的当前位置: > 关注 > > 正文

何删除定时器?MyLibco协程网络库定时器的设计

来源:CSDN 时间:2023-01-28 13:52:43

时间戳类(基本摘自muduo)

//Timestamp.h

namespace Tattoo{class Timestamp{public:    Timestamp();    explicit Timestamp(int64_t microSecondsSinceEpoch);    void swap(Timestamp &that)    {std::swap(microSecondsSinceEpoch_, that.microSecondsSinceEpoch_);    }    std::string toString() const;    std::string toFormattedString() const;    //微妙大于0就是 valid 的    bool valid() const {return microSecondsSinceEpoch_ > 0; }    int64_t microSecondsSinceEpoch() const {return microSecondsSinceEpoch_; }    //微秒转化为秒    time_t secondsSinceEpoch() const    {return static_cast(microSecondsSinceEpoch_ / kMicroSecondsPerSecond);    }    //得到现在的时间    static Timestamp now();    //获取一个无效时间,即时间等于0    static Timestamp invalid();    //一百万,一微秒等于百万分之一秒    static const int kMicroSecondsPerSecond = 1000 * 1000;  private:    int64_t microSecondsSinceEpoch_;};// 这里重载 < 号,在下文的multimap 中就会用到inline bool operator<(Timestamp lhs, Timestamp rhs){return lhs.microSecondsSinceEpoch() < rhs.microSecondsSinceEpoch();}inline bool operator==(Timestamp lhs, Timestamp rhs){return lhs.microSecondsSinceEpoch() == rhs.microSecondsSinceEpoch();}将返回两个事件时间差的秒数,注意单位!inline double timeDifference(Timestamp high, Timestamp low){int64_t diff = high.microSecondsSinceEpoch() - low.microSecondsSinceEpoch();    return static_cast(diff) / Timestamp::kMicroSecondsPerSecond;}//把秒转化为微秒,构造一个对象,再把它们的时间加起来,构造一个无名临时对象返回inline Timestamp addTime(Timestamp timestamp, double seconds){int64_t delta = static_cast(seconds * Timestamp::kMicroSecondsPerSecond);    return Timestamp(timestamp.microSecondsSinceEpoch() + delta);}} // namespace Tattoo


(资料图片仅供参考)

//Timestamp.cpp

using namespace Tattoo;Timestamp::Timestamp()    : microSecondsSinceEpoch_(0){}Timestamp::Timestamp(int64_t microseconds)    : microSecondsSinceEpoch_(microseconds){}std::string Timestamp::toString() const{char buf[32] = {0};    int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond;    int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond;    //PRId64跨平台打印64位整数,因为int64_t用来表示64位整数,在32位系统中是long long int,64位系统中是long int    //所以打印64位是%ld或%lld,可移植性较差,不如统一同PRID64来打印。    snprintf(buf, sizeof(buf) - 1, "%" PRId64 ".%06" PRId64 "", seconds, microseconds);    return buf;}//把它转换成一个格式化字符串std::string Timestamp::toFormattedString() const{char buf[32] = {0};    time_t seconds = static_cast(microSecondsSinceEpoch_ / kMicroSecondsPerSecond);    int microseconds = static_cast(microSecondsSinceEpoch_ % kMicroSecondsPerSecond);    struct tm tm_time;    gmtime_r(&seconds, &tm_time);    snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d.%06d",             tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,             tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec,             microseconds);    return buf;}Timestamp Timestamp::now(){struct timeval tv;    gettimeofday(&tv, NULL);     //获得当前时间,第二个参数是一个时区,当前不需要返回时区,就填空指针    int64_t seconds = tv.tv_sec; //取出秒数    return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);}Timestamp Timestamp::invalid(){return Timestamp();}

定时器

在这里,我是直接让协程在一段时间之后唤醒即可(runAfter),至于需不需要 repeat ,这个我也在思考当中,以后了解到了再加吧!!学习也就是一点一点积累的过程啦!!! //Timer.h

/*定时器类*/class Timer{public:    Timer(Timestamp when);    Timestamp expiration() const {return expire_; }    void run() const;    Timestamp expire_; //任务的超时时间    Routine_t *timer_rou_;};

//Timer.cpp

Timer::Timer(Timestamp when)    : timer_rou_(get_curr_routine()), //一个定时器对应一个协程      expire_(when){}void Timer::run() const{cout << "由定时器唤醒对应协程" << endl;    timer_rou_->Resume();}

定时器容器

.h 文件

class TimeHeap{public:    TimeHeap(EventLoop *loop);    ~TimeHeap();    Timer *addTimer(Timestamp when);    void delTimer(Timer *timer);  private:    typedef std::pairEntry;    typedef std::multimapTimerMap;    // 超时之后的可读回调    void handleRead();    std::vectorgetExpired(Timestamp now);        /* 重置超时的定时器 */    void reset(const std::vector&expired, Timestamp now);    bool insert(Timer *timer);    EventLoop *loop_;    const int timerfd_;    Channel timerfdChannel_;    TimerMap timers_;};

.cpp 文件

namespace Tattoo{namespace detail{//创建 timerfdint createTimerfd(){int timerfd = ::timerfd_create(CLOCK_MONOTONIC,                                   TFD_NONBLOCK | TFD_CLOEXEC);    if (timerfd < 0)    {std::cout << "Failed in timerfd_create" << std::endl;    }    return timerfd;}/* 计算超时时间与当前时间的时间差,并将参数转换为 api 接受的类型  */struct timespec howMuchTimeFromNow(Timestamp when){/* 微秒数 = 超时时刻微秒数 - 当前时刻微秒数 */    int64_t microseconds = when.microSecondsSinceEpoch() - Timestamp::now().microSecondsSinceEpoch();    if (microseconds < 100)    {microseconds = 100;    }    struct timespec ts; // 转换成 struct timespec 结构返回    // tv_sec 秒    // tv_nsec 纳秒    ts.tv_sec = static_cast(        microseconds / Timestamp::kMicroSecondsPerSecond);    ts.tv_nsec = static_cast(        (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);    return ts;}/* 读timerfd,避免定时器事件一直触发 */void readTimerfd(int timerfd, Timestamp now){uint64_t howmany;    ssize_t n = ::read(timerfd, &howmany, sizeof(howmany));    std::cout << "TimerQueue::handleRead() " << howmany << " at " << now.toString() << std::endl;    if (n != sizeof howmany)    {std::cout << "TimerQueue::handleRead() reads " << n << " bytes instead of 8" << std::endl;    }}/* 重置 timerfd 的超时时间 */void resetTimerfd(int timerfd, Timestamp expiration){struct itimerspec newValue;    struct itimerspec oldValue;    bzero(&newValue, sizeof newValue);    bzero(&oldValue, sizeof oldValue);    newValue.it_value = howMuchTimeFromNow(expiration);    //到这个时间后,会产生一个定时事件    int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);    if (ret)    {std::cout << "timerfd_settime()" << std::endl;    }}} // namespace detail} // namespace Tattoousing namespace Tattoo;using namespace Tattoo::detail;TimeHeap::TimeHeap(EventLoop *loop)    : loop_(loop),      timerfd_(createTimerfd()),      timerfdChannel_(loop, timerfd_),      timers_(){// 设置自己独特的回调函数,并不是和普通的Channel 一样,直接唤醒了对应的协程    timerfdChannel_.setHandleCallback(        std::bind(&TimeHeap::handleRead, this));    timerfdChannel_.enableReading();}TimeHeap::~TimeHeap(){timerfdChannel_.disableAll();    ::close(timerfd_);    for (auto it = timers_.begin();         it != timers_.end(); ++it)    {delete it->second;    }}/* 添加一个定时器 ,返回定时器指针,会在 channel->addEpoll 函数中使用到,因为要删除对应的定时器*/Timer *TimeHeap::addTimer(Timestamp when){Timer *timer = new Timer(when);    如果当前插入的定时器 比队列中的定时器都早 则返回真    bool earliestChanged = insert(timer);    //最早的超时时间改变了,就需要重置timerfd_的超时时间    if (earliestChanged)    {//timerfd_ 重新设置超时时间,使得 timerfd  的定时事件始终是最小的        resetTimerfd(timerfd_, timer->expiration());    }    return timer;}/* 删除一个定时器 */void TimeHeap::delTimer(Timer *timer){auto it = timers_.find(timer->expire_);    if (it != timers_.end())    {timers_.erase(it);    }    return;}//timerfd 可读 的回调void TimeHeap::handleRead(){Timestamp now(Timestamp::now());    //先读取    readTimerfd(timerfd_, now);    std::vectorexpired = getExpired(now);    for (std::vector::iterator it = expired.begin();         it != expired.end(); ++it)    {it->second->run(); //run->Resume()    }    reset(expired, now); //这里主要是改变 timerfd 的定时最小值}//获取所有超时的定时器std::vectorTimeHeap::getExpired(Timestamp now){std::vectorexpired;    auto it = timers_.lower_bound(now);    assert(it == timers_.end() || now < it->first);    std::copy(timers_.begin(), it, back_inserter(expired));    timers_.erase(timers_.begin(), it);    return expired;}void TimeHeap::reset(const std::vector&expired, Timestamp now){Timestamp nextExpire;    for (std::vector::const_iterator it = expired.begin();         it != expired.end(); ++it)    {delete it->second;    }    if (!timers_.empty()) //timers_ 不为空    {/*获取当前定时器集合中的最早定时器的时间戳,作为下次超时时间*/        nextExpire = timers_.begin()->second->expiration();    }    //如果取得的时间 >0就改变 timerfd 的定时    if (nextExpire.valid())    {resetTimerfd(timerfd_, nextExpire);    }}bool TimeHeap::insert(Timer *timer){bool earliestChanged = false;    Timestamp when = timer->expiration();    auto it = timers_.begin();    if (it == timers_.end() || when < it->first)    {earliestChanged = true;    }    timers_.insert(std::make_pair(when, timer));    return earliestChanged;}

OK,上面的就是具体的实现代码了,下面来说一下几个点:

1.如何添加定时器?

在我写的协程库中是这样实现的: Channel::addEpoll()->loop_->runAfter(10)->timerHeap_->addTimer()

2.如何删除定时器?

loop_->cancel()->timerHeap_->delTimer()

3.如何将timerfd与Eventloop 统一起来?

首先来看一下eventloop:

.h

#include "Callbacks.h"#include "Timestamp.h"#include#include#include "routine.h"namespace Tattoo{class Channel;class Epoll;class TimeHeap;class Timer;class RoutineEnv_t;class EventLoop{public:    EventLoop();    ~EventLoop();    void loop();    // timers    Timer *runAt(const Timestamp &time);    Timer *runAfter(double delay);    void cancel(Timer *timer);    void updateChannel(Channel *channel);    void removeChannel(Channel *channel);  private:    typedef std::vectorChannelList;    Epoll *epoll_;    TimeHeap *timerHeap_;    ChannelList activeChannels_;    RoutineEnv_t *rouEnv_;};} // namespace Tattoo

.cpp

#include#include "Channel.h"#include "Epoll.h"#include "MiniHeap.h"#include "EventLoop.h"using namespace Tattoo;const int kPollTimeMs = 10000; // 10 sEventLoop::EventLoop()    : rouEnv_(get_curr_thread_env()), //  一个 eventloop  对应一个 Routine_env      epoll_(new Epoll(this)),      timerHeap_(new TimeHeap(this))       //在TimeHead初始化时,就会将 timerfd 加入 epoll 监听中{// std::cout << "EventLoop created " << this << std::endl;    rouEnv_->envEventLoop_ = this; //关键点}EventLoop::~EventLoop(){}void EventLoop::loop(){while (1)    {activeChannels_.clear();        int ret = epoll_->poll(kPollTimeMs, &activeChannels_);        for (auto it = activeChannels_.begin();             it != activeChannels_.end(); ++it)        {(*it)->handleEvent(); //事件分发,记得注册时间回调(一般就是 Resume())        }    }    std::cout << "EventLoop " << this << " stop looping" << std::endl;}Timer *EventLoop::runAt(const Timestamp &time){return timerHeap_->addTimer(time);}Timer *EventLoop::runAfter(double delay){Timestamp time(addTime(Timestamp::now(), delay));    runAt(time);}void EventLoop::cancel(Timer *timer){timerHeap_->delTimer(timer);}void EventLoop::updateChannel(Channel *channel){epoll_->updateChannel(channel);}void EventLoop::removeChannel(Channel *channel){epoll_->removeChannel(channel);}

4.定时器的组织方式(和 muduo 差不多,他用的是set,我用的是 multimap)

muduo定时器容器封装了 Timer.h里面保存的是超时时间和回调函数, TimerQueue.h使用set容器保存多个定时器, 然后在TimerQueue中使用timerfd_create创建一个timerfd句柄, 插入定时器A后先比较A的触发时间和TimerQueue的触发时间, 如果A的触发时间比其小就使用timerfd_settime重置TimerQueue的timerfd的触发时间, TimerQueue中的timerfd的触发时间永远与保存的定时器中触发时间最小的那个相同, 然后timerfd触发可读后, 遍历保存的多个定时器, 看看有没有同时到期的, 有执行回调函数

4.协程库中定时器的使用(与 libco 基本一样)

先行阅读:https://blog.csdn.net/liushengxi_root/article/details/88421955 主要函数(addEpoll):

void Channel::addEpoll(){//这里就设置的回调函数和 timerfd 设置的回调函数不一样哦    setHandleCallback(std::bind(&Channel::handleFun, this));    events_ |= kReadEvent;    events_ |= kWriteEvent;    update();    Timer *tmp = loop_->runAfter(10);    //退出当前协程    get_curr_routine()->Yield();    //删除加入的 epoll 信息和对应定时器    loop_->removeChannel(this);    loop_->cancel(tmp);}

事件到来会唤醒对应的协程,时间超时时 也会唤醒对应的协程(不会让其一直阻塞下去)

主事件循环还是看上面的链接即可!!

运行结果:

责任编辑:

标签:

相关推荐:

精彩放送:

新闻聚焦
Top