timerfd加epoll實現定時器簡單封裝

封裝了一個使用timerfd和epoll實現的定時器

我的思路是

  • 定時器類初始化時候直接啓動一個線程,線程只負責定時事件觸發後派發任務
  • epoll定時器事件觸發的時候,將註冊的定時事件投遞給任務線程
  • 例子中任務線程使用了一個線程池

頭文件

#ifndef CCTimer_H_
#define CCTimer_H_

#include "ThreadPool.h"
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <cstring>
#include <stdint.h>
#include "SafeMap.h"

typedef std::function<void()> TimerCallback;

inline void memZero(void* p, size_t n)
{
    std::memset(p, 0, n);
}


struct TimerInfo{
    int               timerfd;
    TimerCallback     timerFunc;
    uint32_t          timerId;
    bool              isPeriodic;
};
class CCTimer {
public:
    CCTimer(const size_t thread_count = 1);
    ~CCTimer();
    bool setTimeEvent(const uint32_t timerId, const uint32_t ms, TimerCallback cb, const bool isPeriodic = false);
    bool cancelTimeEvent(const uint32_t timerId);
private:
    void handleTimerfdInEpoll();
    bool timerIdIsExist(const uint32_t timerId);
    bool epollAddTimerFd(const uint32_t timerId, const int timerfd);
    bool epollDelTimerFd(const int timerfd);
    bool timerFdSetTime(const int timerfd, const uint32_t ms, const bool isPeriodic = false);
    bool stopTimerfdSetTime(const int timerfd);
    void readTimerfd(int timerfd);
private:
    static const int initEventListSize_ = 16;
    std::shared_ptr<thread_pool> threadPool_;//靈活掌握
    const int epollfd_;
    typedef std::vector<struct epoll_event> EventList;
    ///<觸發的事件填充
    EventList events_;
    SafeMap<uint32_t, TimerInfo>    timerMap_; //這個map如果沒有多線程去操作同一個定時器類對象的話,可以換成普通的map
};
#endif

源文件

#include "Timer.h"
#include <unistd.h>
#include <iostream>
CCTimer::CCTimer(const size_t thread_count):
    epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
    events_(initEventListSize_)
{
    ///<創建線程池
    threadPool_ = std::make_shared<thread_pool>(thread_count);
    ///<創建一個線程做epool事件監聽
    std::thread t(&CCTimer::handleTimerfdInEpoll, this);
    t.detach();
}

CCTimer::~CCTimer()
{
    ::close(epollfd_);
}

bool CCTimer::setTimeEvent(const uint32_t timerId, const uint32_t ms, TimerCallback cb, const bool isPeriodic)
{
    if (timerIdIsExist(timerId)) {
        return false;
    }
    int timerfd = ::timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK | TFD_CLOEXEC);
    if (timerfd < 0) {
        return false;
    }
    if (!timerFdSetTime(timerfd, ms, isPeriodic)) {
        return false;
    }
    if (!epollAddTimerFd(timerId, timerfd)) {
        return false;
    }
    timerMap_[timerId].timerFunc = cb;
    timerMap_[timerId].isPeriodic = isPeriodic;
    return true;
}
bool CCTimer::cancelTimeEvent(const uint32_t timerId)
{
    if (!timerIdIsExist(timerId)) {
        return false;
    }
    if (!stopTimerfdSetTime(timerMap_[timerId].timerfd)) {
        return false;
    }
    ///< 從epoll循環中去掉fd的監聽
    epollDelTimerFd(timerMap_[timerId].timerfd);
    ///< 從map中刪除timerId
    timerMap_.erase(timerId);
    return true;
}

bool CCTimer::timerIdIsExist(const uint32_t timerId)
{
    return timerMap_.find(timerId) != timerMap_.end();
}

bool CCTimer::timerFdSetTime(const int timerfd, const uint32_t ms, const bool isPeriodic)
{
    struct itimerspec newValue;
    memZero(&newValue, sizeof newValue);
    if (ms >= 1000) {
        newValue.it_value.tv_sec = ms / 1000;
    }
    newValue.it_value.tv_nsec = (ms % 1000) * 1000;
    if (isPeriodic) {        
        newValue.it_interval = newValue.it_value;
    }
    if (::timerfd_settime(timerfd, 0, &newValue, NULL)) {
        return false;
    }
    return true;
}

bool CCTimer::stopTimerfdSetTime(const int timerfd)
{
    struct itimerspec newValue;
    memZero(&newValue, sizeof newValue);
    if (::timerfd_settime(timerfd, 0, &newValue, NULL)) {
        return false;
    }
    return true;
}

bool CCTimer::epollAddTimerFd(const uint32_t timerId, const int timerfd)
{
    struct epoll_event event;
    TimerInfo info;
    info.timerfd = timerfd;
    info.timerId = timerId;
    timerMap_[timerId] = info;
    memZero(&event, sizeof event);
    event.data.ptr = &timerMap_[timerId];
    event.events = EPOLLIN;
     if (::epoll_ctl(epollfd_, EPOLL_CTL_ADD, timerfd, &event) < 0) {
        timerMap_.erase(timerId);
        return false;
    }

    return true;
}

bool CCTimer::epollDelTimerFd(const int timerfd)
{
    struct epoll_event event;
    memZero(&event, sizeof event);
    event.events = EPOLLOUT;
    event.data.fd = timerfd;
     if (::epoll_ctl(epollfd_, EPOLL_CTL_DEL, timerfd, &event) < 0) {
        return false;
    }
    return true;
}

void CCTimer::readTimerfd(int timerfd)
{
  uint64_t howmany;
  ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
  if (n != sizeof howmany) {
    ///< error log
    return;
  }
}

void CCTimer::handleTimerfdInEpoll()
{
    while (true) {
        int numEvents = ::epoll_wait(epollfd_, 
            &*events_.begin(),
            static_cast<int>(events_.size()),
            0);
        ///< 事件觸發之後就將函數提交給線程池去執行
        for (int i = 0; i < numEvents; i++) {
            TimerInfo* infoPtr = static_cast<TimerInfo*>(events_[i].data.ptr);
            readTimerfd(infoPtr->timerfd);
            if (threadPool_ == nullptr) {
                infoPtr->timerFunc(); //你要是麼有線程池,就直接執行回調
            } else {
                threadPool_->execute(infoPtr->timerFunc);//推薦其他線程執行,保證定時準確
            }

            if (!infoPtr->isPeriodic) {
                cancelTimeEvent(infoPtr->timerId);
            }
        }
        ///< 說明一次觸發的事件太多,擴大容量
        if (static_cast<size_t>(numEvents) == events_.size()) {
            events_.resize(events_.size()*2);
        }
    }
}

使用例子

#include "Timer.h"
#include <iostream>
#include <chrono>
using namespace std;

void printANum(int a) {
    std::cout << a << std::endl;
}
void printAString(std::string str)
{
    std::cout << str << std::endl;
}
int main (int argc, char** argv) 
{
    CCTimer cc;
    auto f = std::bind(printANum, 100);
    auto f2 = std::bind(printAString, "hahahahhaha");
    cc.setTimeEvent(1, 1000, f, true);
    cc.setTimeEvent(2, 1000, f2, true);
    std::chrono::seconds sec(5);
    std::this_thread::sleep_for(sec);
    cc.cancelTimeEvent(1);
    std::chrono::seconds sec2(5);
    std::this_thread::sleep_for(sec2);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章