1. 程式人生 > >muduo網路庫學習筆記(三)TimerQueue定時器佇列

muduo網路庫學習筆記(三)TimerQueue定時器佇列

目錄

muduo網路庫學習筆記(三)TimerQueue定時器佇列


本章整理muduo中的TimerQueue,會簡述關於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\n";
      timeout.tv_sec = 1;
      timeout.tv_usec = 0;
      FD_SET(timerfd, &rdset);
        continue;
    }
    std::cout << " timer happend\n";
    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

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