• muduo网络库学习笔记(三)TimerQueue定时器队列


    muduo网络库学习笔记(三)TimerQueue定时器队列


    TimerQueue是EventLoop的组件之一,可以提供定时任务,和周期任务。

    本章首先会简述关于timerfd系统定时函数的基本使用,和TimerQueue类的封装结构,最后给出TimerQueue::addTimer()接口的时序图与使用例子.

    Linux中的时间函数

    ·time(2) / time_t(秒)
    ·ftime(3) / struct timeb(毫秒)
    ·gettimeofday(2) / struct timeval(微秒)
    ·clock_gettime(2) / struct timespec(纳秒)
    还有gmtime / localtime / timegm / mktime / strftime / struct tm等与当
    前时间无关的时间格式转换函数。
    定时函数, 用于让程序等待一段时间或安排计划任务:·sleep(3)
    ·alarm(2)
    ·usleep(3)
    ·nanosleep(2)
    ·clock_nanosleep(2)
    ·getitimer(2) / setitimer(2)
    ·timer_create(2) / timer_settime(2) / timer_gettime(2) / timer_delete(2)
    ·timerfd_create(2) / timerfd_gettime(2) / timerfd_settime(2)

    muduo中做的取舍如下
    ·(计时) 只使用gettimeofday(2)来获取当前时间。
    ·(定时) 只使用timerfd_*系列函数来处理定时任务。

    gettimeofday(2)入选原因(这也是muduo::Timestamp class的主要设计考虑) :
    1. time(2)的精度太低, ftime(3)已被废弃; clock_gettime(2)精度最高, 但是其系统调用的开销比gettimeofday(2)大。
    2. 在x86-64平台上, gettimeofday(2)不是系统调用, 而是在用户态实现的, 没有上下文切换和陷入内核的开销32。
    3. gettimeofday(2)的分辨率(resolution) 是1微秒, 现在的实现确实能达到这个计时精度, 足以满足日常计时的需要。 muduo::Timestamp用一个int64_t来表示从Unix Epoch到现在的微秒数, 其范围可达上下30万年。

    timerfd_*入选的原因:
    1. sleep(3) / alarm(2) / usleep(3)在实现时有可能用了SIGALRM信号, 在多线程程序中处理信号是个相当麻烦的事情, 应当尽量避免, 再说, 如果主程序和程序库都使用SIGALRM, 就糟糕了。
    2. nanosleep(2)和clock_nanosleep(2)是线程安全的, 但是在非阻塞网络编程中, 绝对不能用让线程挂起的方式来等待一段时间, 这样一来程序会失去响应。 正确的做法是注册一个时间回调函数。
    3. getitimer(2)和timer_create(2)也是用信号来deliver超时,在多线程程序中也会有麻烦。timer_create(2)可以指定信号的接收方是进程还是线程, 算是一个进步, 不过信号处理函数(signal handler) 能做的事情实在很受限。
    4.timerfd_create(2)把时间变成了一个文件描述符, 该“文件”在定时器超时的那一刻变得可读, 这样就能很方便地融入select(2)/poll(2)框架中, 用统一的方式来处理IO事件和超时事件, 这也正是Reactor模式的长处。
    5. 传统的Reactor利用select(2)/poll(2)/epoll(4)的timeout来实现定时功能, 但poll(2)和epoll_wait(2)的定时精度只有毫秒,远低于timerfd_settime(2)的定时精度。


    timerfd简单使用介绍

    本章使用到的两个系统函数:

    #include <sys/timerfd.h>
    int timerfd_create(int clockid, int flags);
    int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
    

    1、timerfd_create函数生成一个定时器,返回与之关联的文件描述,其中的clockid可以设成CLOCK_REALTIME和CLOCK_MONOTONIC
    CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
    CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响

    2、timerfd_settime用于启停定时器,new_value为超时时间,old_value为周期性定时时间,为0表示不进行周期性定时.

     struct timespec {
          time_t tv_sec;                /* Seconds */
          long   tv_nsec;               /* Nanoseconds */
      };
    
      struct itimerspec {
         struct timespec it_interval;  /* Interval for periodic timer */
         struct timespec it_value;     /* Initial expiration */
      };
    

    timerfd示例

    通过select 监听timerfd,可读时表明到达定时时间.

    #include <sys/timerfd.h>
    #include <sys/select.h>
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    
    int main()
    {
    
      int timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK |TFD_CLOEXEC);
    
      struct itimerspec howlong;
      bzero(&howlong, sizeof howlong);
      howlong.it_value.tv_sec = 3;
      timerfd_settime(timerfd, 0, &howlong, NULL);
    
      fd_set rdset;
      FD_ZERO(&rdset);
      FD_SET(timerfd, &rdset);
    
      struct timeval timeout;
      timeout.tv_sec = 1;
      timeout.tv_usec = 0;
      while(1)
      {
        if(select(timerfd + 1, &rdset, NULL, NULL, &timeout) == 0)
        {
          std::cout << "timeout
    ";
          timeout.tv_sec = 1;
          timeout.tv_usec = 0;
          FD_SET(timerfd, &rdset);
            continue;
        }
        std::cout << " timer happend
    ";
        break;
      }
    
    
      close(timerfd);
    
      return 0;
    }
    
    /* print
    timeout
    timeout
    timer happend
    */
    
    

    muduo中对timerfd的封装

    int createTimerfd()
    {
      int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                                     TFD_NONBLOCK | TFD_CLOEXEC);
      if (timerfd < 0)
      {
        std::cout << "Failed in timerfd_create" << std::endl;
        abort();
      }
      return timerfd;
    }
    
    struct timespec howMuchTimeFromNow(TimeStamp when)
    {
      int64_t microseconds = when.microSecondsSinceEpoch()
                             - TimeStamp::now().microSecondsSinceEpoch();
      if (microseconds < 100)
      {
        microseconds = 100;
      }
      struct timespec ts;
      ts.tv_sec = static_cast<time_t>(
          microseconds / TimeStamp::kMicroSecondsPerSecond);
      ts.tv_nsec = static_cast<long>(
          (microseconds % TimeStamp::kMicroSecondsPerSecond) * 1000);
      return ts;
    }
    
    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;
      }
    }
    
    void resetTimerfd(int timerfd, TimeStamp expiration)
    {
      // wake up loop by timerfd_settime()
      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;
      }
    }
    
    

    TimerQueue的结构.

    TimerQueue只提供了两个对外的接口
    addTimer() 添加一个定时器超时执行回调函数cb. interval是周期性定时时间,本章不讲,置零不打开.
    cancel() 取消一个定时器,本章也不细述,本章只介绍addTimer()接口.后面如果需要会补充本章.

    TimerQueue定义如下

    class TimerQueue
    {
    public:
      TimerQueue(EventLoop* loop);
      ~TimerQueue();
    
      // Schedules the callback to be run at given time,
    
      TimerId addTimer(const NetCallBacks::TimerCallBack& cb, TimeStamp when, double interval = 0.0);
    
      void cancel(TimerId timerId);
    
    private:
      /*
        .....
      */
      EventLoop* p_loop;
      const int m_timerfd;
      Channel m_timerfdChannel;
    
      //Timer List sorted by expiration
      TimerList m_timers;
      ActiveTimerSet m_activeTimers;
    };
    

    虽然只提供了两个对外接口,但是私有成比较复杂,首先简介成员Timer.

    Timer

    Timer类作为TimerQueue的内部成员使用,封装一个定时器,addTimer()接口调用后生成一个Timer(),外头的回调函数和定时反应时间会封装再此Timer中.
    Tiemr主要接口都是获取这些构造时配置的成员值
    run() 调用回调函数.
    expiration 定时器过期时间.
    interval 周期性定时时间.
    repeat 是否是周期性定时.
    sequence 一个静态成员,用于记录Timer创建的个数. 为了保证它的线程安全性,使用AtomicInt64封装了一层原子操作.

    TimerId 用于Cancel Timer的标志Id, 这两个类都不复杂,可自行参看muduo源码,也可翻我github上的SimpleMuduo单独类文件夹下的测试代码.

      Timer(const NetCallBacks::TimerCallBack& cb, TimeStamp when, double interval)
      :m_callBack(cb),
      m_expiration(when),
      m_interval(interval),
      m_repeat(interval > 0.0),
      m_sequence(s_numCreated.incrementAndGet())
    
    TimerId TimerQueue::addTimer(const NetCallBacks::TimerCallBack& cb, TimeStamp when, double interval)
    {
      Timer* timer = new Timer(cb, when, interval);
      //p_loop->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer));
      return TimerId(timer, timer->sequence());
    }
    

    Timer的容器.

      typedef std::pair<TimeStamp, Timer*> Entry;
      typedef std::set<Entry> TimerList;
      typedef std::pair<Timer*, int64_t> ActiveTimer;
      typedef std::set<ActiveTimer> ActiveTimerSet;
    

    为了解决无法处理两个Timer到期时间相同的情况。使用了pair将时间戳和Timer的地址组成了一对.然后使用Set存储.
    ActiveTimer 将Timer和sequence组成一对主要作用来索引迭代的.

    TimerQueue私有接口介绍.

      void addTimerInLoop(Timer* timer);
      void cancelInLoop(TimerId timerId);
      //called when timerfd alarms
      void handleRead();
      //move out all expired timers and return they.
      std::vector<Entry> getExpired(TimeStamp now);
      bool insert(Timer* timer);
      void reset(const std::vector<Entry>& expired, TimeStamp now);
    
    

    添加定时器

    bool insert(Timer* timer); //插入一个定时器.

    EventLoop添加了一个runInLoop接口: 在它的IO线程内执行某个用户
    任务回调, 即EventLoop::runInLoop(const Functor& cb), 其中Functor是
    std::function<void()>。 如果用户在当前IO线程调用这个函数, 回调会
    同步进行; 如果用户在其他线程调用runInLoop(), cb会被加入队列, IO
    线程会被唤醒来调用这个Functor。

    addTimer()使用了EventLoop的runInLoop接口(这个接口主要来保证线程安全性的,暂不讲),来执行addTimerInLoop().addTimerInLoop()调用insert()插入定时器.
    如果此定时器是最早触发的那一个则会调用resetTimerfd()->timerfd_settime()启动定时器.

    addTimer()->addTimerInLoop()->insert()->resetTimerfd()->timerfd_settime()

    更新定时器

    添加逻辑就在上面了,下面给出处理逻辑.
    三个接口.
    void handleRead(); //定时器触发回调.获取已过期的事件并处理随后更新列表.
    std::vector getExpired(TimeStamp now);//获取一组已过期的定时器,并从TimerList中删除.
    void reset(const std::vector& expired, TimeStamp now);//如果是周期性任务则重新配置时间插入,随后调用resetTimerfd()更新下一定时任务.

    handleRead()->getExpired()->Timer->run(cb)->reset()->resetTimerfd()->timerfd_settime()

    时序图

    TimerQueue源码

    #ifndef _NET_TIMERQUEUE_HH
    #define _NET_TIMERQUEUE_HH
    #include "TimerId.hh"
    #include "CallBacks.hh"
    #include "TimeStamp.hh"
    #include "Channel.hh"
    #include <set>
    #include <vector>
    
    class EventLoop;
    
    class TimerQueue
    {
    public:
      TimerQueue(EventLoop* loop);
      ~TimerQueue();
    
      // Schedules the callback to be run at given time,
    
      TimerId addTimer(const NetCallBacks::TimerCallBack& cb, TimeStamp when, double interval = 0.0);
    
      void cancel(TimerId timerId);
    
    private:
      typedef std::pair<TimeStamp, Timer*> Entry;
      typedef std::set<Entry> TimerList;
      typedef std::pair<Timer*, int64_t> ActiveTimer;
      typedef std::set<ActiveTimer> ActiveTimerSet;
    
      void addTimerInLoop(Timer* timer);
      void cancelInLoop(TimerId timerId);
      //called when timerfd alarms
      void handleRead();
      //move out all expired timers and return they.
      std::vector<Entry> getExpired(TimeStamp now);
      bool insert(Timer* timer);
      void reset(const std::vector<Entry>& expired, TimeStamp now);
    
      EventLoop* p_loop;
      const int m_timerfd;
      Channel m_timerfdChannel;
    
      //Timer List sorted by expiration
      TimerList m_timers;
      ActiveTimerSet m_activeTimers;
    
      bool m_callingExpiredTimers; /*atomic*/
      ActiveTimerSet m_cancelingTimers;
    
    };
    
    #endif
    
    //TimerQueeu.cpp
    
    #include <stdint.h>
    #include <assert.h>
    #include <sys/timerfd.h>
    #include <unistd.h>
    
    #include "Logger.hh"
    #include "EventLoop.hh"
    #include "Timer.hh"
    #include "TimerQueue.hh"
    
    namespace TimerFd
    {
    
    int createTimerfd()
    {
      int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                                     TFD_NONBLOCK | TFD_CLOEXEC);
      if (timerfd < 0)
      {
        LOG_SYSFATAL << "Failed in timerfd_create";
      }
      return timerfd;
    }
    
    struct timespec howMuchTimeFromNow(TimeStamp when)
    {
      int64_t microseconds = when.microSecondsSinceEpoch()
                             - TimeStamp::now().microSecondsSinceEpoch();
      if (microseconds < 100)
      {
        microseconds = 100;
      }
      struct timespec ts;
      ts.tv_sec = static_cast<time_t>(
          microseconds / TimeStamp::kMicroSecondsPerSecond);
      ts.tv_nsec = static_cast<long>(
          (microseconds % TimeStamp::kMicroSecondsPerSecond) * 1000);
      return ts;
    }
    
    void readTimerfd(int timerfd, TimeStamp now)
    {
      uint64_t howmany;
      ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
      LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
      if (n != sizeof howmany)
      {
        LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
      }
    }
    
    void resetTimerfd(int timerfd, TimeStamp expiration)
    {
      // wake up loop by timerfd_settime()
      LOG_TRACE << "resetTimerfd()";
      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)
      {
        LOG_SYSERR << "timerfd_settime()";
      }
    }
    
    };
    
    using namespace TimerFd;
    
    TimerQueue::TimerQueue(EventLoop* loop)
      :p_loop(loop),
       m_timerfd(createTimerfd()),
       m_timerfdChannel(p_loop, m_timerfd),
       m_timers(),
       m_callingExpiredTimers(false)
    {
      m_timerfdChannel.setReadCallBack(std::bind(&TimerQueue::handleRead, this));
      m_timerfdChannel.enableReading();
    }
    
    TimerQueue::~TimerQueue()
    {
      m_timerfdChannel.disableAll();
      m_timerfdChannel.remove();
      ::close(m_timerfd);
      for (TimerList::iterator it = m_timers.begin();
          it != m_timers.end(); ++it)
      {
        delete it->second;
      }
    }
    
    std::vector<TimerQueue::Entry> TimerQueue::getExpired(TimeStamp now)
    {
      std::vector<Entry> expired;
      Entry sentry = std::make_pair(now, reinterpret_cast<Timer*>UINTPTR_MAX);
      TimerList::iterator it = m_timers.lower_bound(sentry);
      assert(it == m_timers.end() || now < it->first);
      std::copy(m_timers.begin(), it, back_inserter(expired));
      m_timers.erase(m_timers.begin(), it);
    
      for(std::vector<Entry>::iterator it = expired.begin();
          it != expired.end(); ++it)
      {
        ActiveTimer timer(it->second, it->second->sequence());
        size_t n = m_activeTimers.erase(timer);
        assert(n == 1); (void)n;
      }
    
      assert(m_timers.size() == m_activeTimers.size());
    
      return expired;
    }
    
    
    TimerId TimerQueue::addTimer(const NetCallBacks::TimerCallBack& cb, TimeStamp when, double interval)
    {
      Timer* timer = new Timer(cb, when, interval);
      p_loop->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer));
      return TimerId(timer, timer->sequence());
    }
    
    void TimerQueue::addTimerInLoop(Timer* timer)
    {
      p_loop->assertInLoopThread();
      bool earliestChanged = insert(timer);
    
      if (earliestChanged)
      {
        resetTimerfd(m_timerfd, timer->expiration());
      }
    }
    
    void TimerQueue::cancel(TimerId timerId)
    {
      p_loop->runInLoop(std::bind(&TimerQueue::cancelInLoop, this, timerId));
    }
    
    void TimerQueue::cancelInLoop(TimerId timerId)
    {
      p_loop->assertInLoopThread();
      assert(m_timers.size() ==  m_activeTimers.size());
      ActiveTimer timer(timerId.m_timer, timerId.m_sequence);
      ActiveTimerSet::iterator it = m_activeTimers.find(timer);
      if(it != m_activeTimers.end())
      {
        size_t n = m_timers.erase(Entry(it->first->expiration(), it->first));
        assert(n == 1);
        delete it->first;
      }
      else if (m_callingExpiredTimers)
      {
        m_cancelingTimers.insert(timer);
      }
      assert(m_timers.size() == m_activeTimers.size());
    }
    
    bool TimerQueue::insert(Timer* timer)
    {
      p_loop->assertInLoopThread();
      assert(m_timers.size() == m_activeTimers.size());
      bool earliestChanged = false;
      TimeStamp when = timer->expiration();
      TimerList::iterator it = m_timers.begin();
      if (it == m_timers.end() || when < it->first)
      {
        earliestChanged = true;
      }
      {
        std::pair<TimerList::iterator, bool> result
          = m_timers.insert(Entry(when, timer));
        assert(result.second); (void)result;
      }
      {
        std::pair<ActiveTimerSet::iterator, bool> result
          = m_activeTimers.insert(ActiveTimer(timer, timer->sequence()));
        assert(result.second); (void)result;
      }
    
      LOG_TRACE << "TimerQueue::insert() " << "m_timers.size() : "
      << m_timers.size() << " m_activeTimers.size() : " << m_activeTimers.size();
    
      assert(m_timers.size() == m_activeTimers.size());
      return earliestChanged;
    }
    
    
    void TimerQueue::handleRead()
    {
      p_loop->assertInLoopThread();
      TimeStamp now(TimeStamp::now());
      readTimerfd(m_timerfd, now);
    
      std::vector<Entry> expired = getExpired(now);
    
      LOG_TRACE << "Expired Timer size " << expired.size() << "  ";
    
      m_callingExpiredTimers = true;
      m_cancelingTimers.clear();
    
      for(std::vector<Entry>::iterator it = expired.begin();
          it != expired.end(); ++it )
      {
        it->second->run();
      }
    
      m_callingExpiredTimers = false;
    
      reset(expired, now);
    }
    
    
    void TimerQueue::reset(const std::vector<Entry>& expired, TimeStamp now)
    {
      TimeStamp nextExpire;
    
      for(std::vector<Entry>::const_iterator it = expired.begin();
          it != expired.end(); ++it)
      {
        ActiveTimer timer(it->second, it->second->sequence());
        if(it->second->repeat()
          && m_cancelingTimers.find(timer) == m_cancelingTimers.end())
        {//如果是周期定时器则重新设定时间插入. 否则delete.
          it->second->restart(now);
          insert(it->second);
        }
        else
        {// FIXME move to a free list no delete please
          delete it->second;
        }
      }
    
      if (!m_timers.empty())
      {
        nextExpire = m_timers.begin()->second->expiration();
      }
    
      if (nextExpire.valid())
      {
        resetTimerfd(m_timerfd, nextExpire);
      }
    }
    
    

    TimerQueue使用示例

    测试TimerQueue的addTimer接口.

    #include <errno.h>
    #include <thread>
    #include <strings.h>
    #include <poll.h>
    #include <functional>
    #include "EventLoop.hh"
    #include "Channel.hh"
    #include "Poller.hh"
    #include "Logger.hh"
    #include "Timer.hh"
    #include "TimeStamp.hh"
    #include "TimerQueue.hh"
    
    EventLoop* g_loop;
    
    void print() { LOG_DEBUG << "test print()";  }
    
    void test()
    {
    
      LOG_DEBUG << "[test] : test timerQue";
    
    }
    
    
    int main()
    {
      EventLoop loop;
      g_loop = &loop;
    
      TimerQueue timerQue(&loop);
      timerQue.addTimer(test, times::addTime(TimeStamp::now(), 3.0));
      timerQue.addTimer(test, times::addTime(TimeStamp::now(), 3.0));
      timerQue.addTimer(test, times::addTime(TimeStamp::now(), 5.0));
    
      loop.loop();
    
      return 0;
    }
    
    
    
    ./test.out 
    2018-11-11 15:49:22.493990 [TRACE] [Poller.cpp:64] [updateChannel] fd= 3 events3
    2018-11-11 15:49:22.494042 [TRACE] [EventLoop.cpp:34] [EventLoop] EventLoop Create 0x7FFFBD3C39B0 in thread 3262
    2018-11-11 15:49:22.494047 [TRACE] [Poller.cpp:64] [updateChannel] fd= 5 events3
    2018-11-11 15:49:22.494055 [TRACE] [TimerQueue.cpp:172] [insert] TimerQueue::insert() m_timers.size() : 1 m_activeTimers.size() : 1
    2018-11-11 15:49:22.494058 [TRACE] [TimerQueue.cpp:55] [resetTimerfd] resetTimerfd()
    2018-11-11 15:49:22.494066 [TRACE] [TimerQueue.cpp:172] [insert] TimerQueue::insert() m_timers.size() : 2 m_activeTimers.size() : 2
    2018-11-11 15:49:22.494070 [TRACE] [TimerQueue.cpp:172] [insert] TimerQueue::insert() m_timers.size() : 3 m_activeTimers.size() : 3
    2018-11-11 15:49:22.494073 [TRACE] [EventLoop.cpp:59] [loop] EventLoop 0x7FFFBD3C39B0 start loopig
    2018-11-11 15:49:22.494075 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:23.495462 [TRACE] [Poller.cpp:28] [poll]  nothing happended
    2018-11-11 15:49:23.495488 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:24.496618 [TRACE] [Poller.cpp:28] [poll]  nothing happended
    2018-11-11 15:49:24.496640 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:25.494222 [TRACE] [Poller.cpp:24] [poll] 1 events happended
    2018-11-11 15:49:25.494253 [TRACE] [TimerQueue.cpp:45] [readTimerfd] TimerQueue::handleRead() 1 at 1541922565.494251
    2018-11-11 15:49:25.494316 [TRACE] [TimerQueue.cpp:188] [handleRead] Expired Timer size 2  
    
    2018-11-11 15:49:25.494320 [DEBUG] [main.cpp:50] [test] [test] : test timerQue
    2018-11-11 15:49:25.494322 [DEBUG] [main.cpp:50] [test] [test] : test timerQue
    
    2018-11-11 15:49:25.494325 [TRACE] [TimerQueue.cpp:55] [resetTimerfd] resetTimerfd()
    2018-11-11 15:49:25.494332 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:26.496293 [TRACE] [Poller.cpp:28] [poll]  nothing happended
    2018-11-11 15:49:26.496318 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:27.494291 [TRACE] [Poller.cpp:24] [poll] 1 events happended
    2018-11-11 15:49:27.494319 [TRACE] [TimerQueue.cpp:45] [readTimerfd] TimerQueue::handleRead() 1 at 1541922567.494317
    2018-11-11 15:49:27.494328 [TRACE] [TimerQueue.cpp:188] [handleRead] Expired Timer size 1  
    
    2018-11-11 15:49:27.494331 [DEBUG] [main.cpp:50] [test] [test] : test timerQue
    
    2018-11-11 15:49:27.494334 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
    2018-11-11 15:49:28.495665 [TRACE] [Poller.cpp:28] [poll]  nothing happended
    
    
  • 相关阅读:
    ios NSString format 保留小数点 float double
    IOS中延时执行的几种方式的比较和汇总
    ioss使用xcode常用快捷键
    iphone 6plus 下app里的状态栏和界面会被放大的问题//以及设置APP闪屏页/APP图标流程
    iostbleView刷新后显示指定cell
    iOS-打包成ipa的4种方法
    iosttableViewCell右侧的箭头,圆形等
    Linux学习之CentOS(二十)------vi/vim 按键说明
    gzip
    bzip2
  • 原文地址:https://www.cnblogs.com/ailumiyana/p/9942989.html
Copyright © 2020-2023  润新知