C++实现多级时间轮定时器

最近实现了一个多级时间轮定时器,经过了简单的测试,如果将时钟步长设置为10ms以内误差会比较大。最新代码地址TimeWheel Github

特性

  • C++11实现
  • 可自定义时间轮层级
  • 可自定义时间轮刻度
  • 可设置时钟步长
  • 误差基本在时钟步长以内
  • 推荐时钟步长50ms

类介绍

  • 代码由三个类组成:Timer,TimeWheel,TimeWheelScheduler。
    • Timer:定时器类,封装了定时器任务std::function<void()>
    • TimeWheel:时间轮类,每个时间轮都有自己的刻度,当低级的时间轮刻度转满一圈的时候,当前时间轮进行进位,即当前时间轮刻度加一。每个时间轮刻度可设置为N,在每个刻度都对应了一个双向链表来保存该时刻的定时器std::vector<std::list<TimerPtr>>(N)
    • TimeWheelScheduler:时间轮调度,启动一个线程按照固定的时钟步长去运行时间轮。提供创建定时器的接口CreateTimerAtCreateTimerAfterCreateTimerEvery

例子

int main() {
  // Four level time wheels: Hour, Minute, Secon, Millisecond.
  int timer_step_ms = 50;
  TimeWheelScheduler tws(timer_step_ms);
  // Hour time wheel. 24 scales, 1 scale = 60 * 60 * 1000 ms.
  tws.AppendTimeWheel(24, 60 * 60 * 1000, "HourTimeWheel");
  // Minute time wheel. 60 scales, 1 scale = 60 * 1000 ms.
  tws.AppendTimeWheel(60, 60 * 1000, "MinuteTimeWheel");
  // Second time wheel. 60 scales, 1 scale = 1000 ms.
  tws.AppendTimeWheel(60, 1000, "SecondTimeWheel");
  // Millisecond time wheel. 1000/timer_step_ms scales, 1 scale = timer_step ms.
  tws.AppendTimeWheel(1000 / timer_step_ms, timer_step_ms, "MillisecondTimeWheel");

  tws.Start();

  tws.CreateTimerAt(GetNowTimestamp() + 10000, []() {
    std::cout << "At now+10s" << std::endl;
  });

  tws.CreateTimerAfter(500, []() {
    std::cout << "After 0.5s" << std::endl;
  });

  std::cout << timetoStr() << std::endl;
  auto timer_id = tws.CreateTimerEvery(5000, []() {
    std::cout << "Every 5s: " << timetoStr() << std::endl;
  });

  tws.CreateTimerEvery(30 * 1000, []() {
    std::cout << "Every 30s: " << timetoStr() <<std::endl;
  });

  tws.CancelTimer(timer_id);

  std::this_thread::sleep_for(std::chrono::minutes(20));
  tws.Stop();

  return 0;
}

该例创建了一个四级的时间轮:小时级(高级时间轮),分钟级,秒级,毫秒级(低级时间轮)。

  • 小时级:以1小时为1个刻度,总共24小时则24个刻度。1小时等于60*60*1000毫秒。tws.AppendTimeWheel(24, 60 * 60 * 1000, "HourTimeWheel")

  • 分钟级:以1分钟为1个刻度,总共60分钟则60个刻度。1分钟等于60*1000毫秒。tws.AppendTimeWheel(60, 60 * 1000, "MinuteTimeWheel");

  • 秒级:以1秒为1个刻度,总共60秒则60个刻度。1秒等于1000毫秒。tws.AppendTimeWheel(60, 1000, "SecondTimeWheel");

  • 毫秒级:以1个时钟步长timer_step_ms为1个单位,1秒等于1000毫秒,那么则有1000/timer_step_ms个刻度。tws.AppendTimeWheel(1000 / timer_step_ms, timer_step_ms, "MillisecondTimeWheel");

代码

timer.h

#ifndef TIMER_H_
#define TIMER_H_

#include <cstdint>
#include <functional>
#include <memory>

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

class Timer {
public:
  Timer(uint32_t id, int64_t when_ms, int64_t interval_ms, const TimerTask& task);

  void Run();

  uint32_t id() const {
    return id_;
  }

  int64_t when_ms() const {
    return when_ms_;
  }

  bool repeated() const {
    return repeated_;
  }

  void UpdateWhenTime() {
    when_ms_ += interval_ms_;
  }

private:
  uint32_t id_;
  TimerTask task_;
  int64_t when_ms_;
  uint32_t interval_ms_;
  bool repeated_;
};

using TimerPtr = std::shared_ptr<Timer>;

#endif  // TIMER_H_

timer.cpp

#include "timer.h"

Timer::Timer(uint32_t id, int64_t when_ms, int64_t interval_ms, const TimerTask& handler)
    : interval_ms_(interval_ms)
    , repeated_(interval_ms_ > 0)
    , when_ms_(when_ms)
    , id_(id)
    , task_(handler) {
}

void Timer::Run() {
  if (task_) {
    task_();
  }
}

time_wheel.h

#ifndef TIME_WHEEL_H_
#define TIME_WHEEL_H_

#include <chrono>
#include <string>
#include <memory>
#include <vector>
#include <list>

#include "timer.h"

class TimeWheel {
public:
  TimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name = "");

  uint32_t scale_unit_ms() const {
    return scale_unit_ms_;
  }

  uint32_t scales() const {
    return scales_;
  }

  uint32_t current_index() const {
    return current_index_;
  }

  void set_less_level_tw(TimeWheel* less_level_tw) {
    less_level_tw_ = less_level_tw;
  }

  void set_greater_level_tw(TimeWheel* greater_level_tw) {
    greater_level_tw_ = greater_level_tw;
  }

  int64_t GetCurrentTime() const;

  void AddTimer(TimerPtr timer);

  void Increase();

  std::list<TimerPtr> GetAndClearCurrentSlot();

private:
  std::string name_;
  uint32_t current_index_;

  // A time wheel can be devided into multiple scales. A scals has N ms.
  uint32_t scales_;
  uint32_t scale_unit_ms_;

  // Every slot corresponds to a scale. Every slot contains the timers.
  std::vector<std::list<TimerPtr>> slots_;

  TimeWheel* less_level_tw_;  // Less scale unit.
  TimeWheel* greater_level_tw_;  // Greater scale unit.
};

using TimeWheelPtr = std::shared_ptr<TimeWheel>;

inline int64_t GetNowTimestamp() {
  using namespace std::chrono;
  auto now = system_clock::now().time_since_epoch();
  return duration_cast<milliseconds>(now).count();
}

#endif  // TIME_WHEEL_H_

time_wheel.cpp

#include "time_wheel.h"

TimeWheel::TimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name)
    : name_(name)
    , current_index_(0)
    , scales_(scales)
    , scale_unit_ms_(scale_unit_ms)
    , slots_(scales)
    , greater_level_tw_(nullptr)
    , less_level_tw_(nullptr) {
}

int64_t TimeWheel::GetCurrentTime() const {
  int64_t time = current_index_ * scale_unit_ms_;
  if (less_level_tw_ != nullptr) {
    time += less_level_tw_->GetCurrentTime();
  }

  return time;
}

void TimeWheel::AddTimer(TimerPtr timer) {
  int64_t less_tw_time = 0;
  if (less_level_tw_ != nullptr) {
    less_tw_time = less_level_tw_->GetCurrentTime();
  }
  int64_t diff = timer->when_ms() + less_tw_time - GetNowTimestamp();

  // If the difference is greater than scale unit, the timer can be added into the current time wheel.
  if (diff >= scale_unit_ms_) {
    size_t n = (current_index_ + diff / scale_unit_ms_) % scales_;
    slots_[n].push_back(timer);
    return;
  }

  // If the difference is less than scale uint, the timer should be added into less level time wheel.
  if (less_level_tw_ != nullptr) {
    less_level_tw_->AddTimer(timer);
    return;
  }

  // If the current time wheel is the least level, the timer can be added into the current time wheel.
  slots_[current_index_].push_back(timer);
}

void TimeWheel::Increase() {
  // Increase the time wheel.
  ++current_index_;
  if (current_index_ < scales_) {
    return;
  }

  // If the time wheel is full, the greater level time wheel should be increased.
  // The timers in the current slot of the greater level time wheel should be moved into
  // the less level time wheel.
  current_index_ = current_index_ % scales_;
  if (greater_level_tw_ != nullptr) {
    greater_level_tw_->Increase();
    std::list<TimerPtr> slot = std::move(greater_level_tw_->GetAndClearCurrentSlot());
    for (TimerPtr timer : slot) {
      AddTimer(timer);
    }
  }
}

std::list<TimerPtr> TimeWheel::GetAndClearCurrentSlot() {
  std::list<TimerPtr> slot;
  slot = std::move(slots_[current_index_]);
  return slot;
}

time_wheel_scheduler.h

#ifndef TIME_WHEEL_SCHEDULER_H_
#define TIME_WHEEL_SCHEDULER_H_

#include <mutex>
#include <vector>
#include <thread>
#include <unordered_set>

#include "time_wheel.h"

class TimeWheelScheduler {
public:
  explicit TimeWheelScheduler(uint32_t timer_step_ms = 50);

  // Return timer id. Return 0 if the timer creation fails.
  uint32_t CreateTimerAt(int64_t when_ms, const TimerTask& handler);
  uint32_t CreateTimerAfter(int64_t delay_ms, const TimerTask& handler);
  uint32_t CreateTimerEvery(int64_t interval_ms, const TimerTask& handler);

  void CancelTimer(uint32_t timer_id);

  bool Start();
  void Stop();

  void AppendTimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name = "");

private:
  void Run();

  TimeWheelPtr GetGreatestTimeWheel();
  TimeWheelPtr GetLeastTimeWheel();

private:
  std::mutex mutex_;
  std::thread thread_;

  bool stop_;

  std::unordered_set<uint32_t> cancel_timer_ids_;

  uint32_t timer_step_ms_;
  std::vector<TimeWheelPtr> time_wheels_;
};

#endif  // TIME_WHEEL_SCHEDULER_H_

time_wheel_scheduler.cpp

#include "time_wheel_scheduler.h"

static uint32_t s_inc_id = 1;

TimeWheelScheduler::TimeWheelScheduler(uint32_t timer_step_ms)
    : timer_step_ms_(timer_step_ms)
    , stop_(false) {
}

bool TimeWheelScheduler::Start() {
  if (timer_step_ms_ < 50) {
    return false;
  }
  
  if (time_wheels_.empty()) {
    return false;
  }

  thread_ = std::thread(std::bind(&TimeWheelScheduler::Run, this));

  return true;
}

void TimeWheelScheduler::Run() {
  while (true) {
    std::this_thread::sleep_for(std::chrono::milliseconds(timer_step_ms_));

    std::lock_guard<std::mutex> lock(mutex_);
    if (stop_) {
      break;
    }

    TimeWheelPtr least_time_wheel = GetLeastTimeWheel();
    least_time_wheel->Increase();
    std::list<TimerPtr> slot = std::move(least_time_wheel->GetAndClearCurrentSlot());
    for (const TimerPtr& timer : slot) {
      auto it = cancel_timer_ids_.find(timer->id());
      if (it != cancel_timer_ids_.end()) {
        cancel_timer_ids_.erase(it);
        continue;
      }

      timer->Run();
      if (timer->repeated()) {
        timer->UpdateWhenTime();
        GetGreatestTimeWheel()->AddTimer(timer);
      }
    }
  }
}

void TimeWheelScheduler::Stop() {
  {
    std::lock_guard<std::mutex> lock(mutex_);
    stop_ = true;
  }

  thread_.join();
}

TimeWheelPtr TimeWheelScheduler::GetGreatestTimeWheel() {
  if (time_wheels_.empty()) {
    return TimeWheelPtr();
  }

  return time_wheels_.front();
}

TimeWheelPtr TimeWheelScheduler::GetLeastTimeWheel() {
  if (time_wheels_.empty()) {
    return TimeWheelPtr();
  }

  return time_wheels_.back();
}

void TimeWheelScheduler::AppendTimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name) {
  TimeWheelPtr time_wheel = std::make_shared<TimeWheel>(scales, scale_unit_ms, name);
  if (time_wheels_.empty()) {
    time_wheels_.push_back(time_wheel);
    return;
  }

  TimeWheelPtr greater_time_wheel = time_wheels_.back();
  greater_time_wheel->set_less_level_tw(time_wheel.get());
  time_wheel->set_greater_level_tw(greater_time_wheel.get());
  time_wheels_.push_back(time_wheel);
}

uint32_t TimeWheelScheduler::CreateTimerAt(int64_t when_ms, const TimerTask& handler) {
  if (time_wheels_.empty()) {
    return 0;
  }

  std::lock_guard<std::mutex> lock(mutex_);
  ++s_inc_id;
  GetGreatestTimeWheel()->AddTimer(std::make_shared<Timer>(s_inc_id, when_ms, 0, handler));

  return s_inc_id;
}

uint32_t TimeWheelScheduler::CreateTimerAfter(int64_t delay_ms, const TimerTask& handler) {
  int64_t when = GetNowTimestamp() + delay_ms;
  return CreateTimerAt(when, handler);
}

uint32_t TimeWheelScheduler::CreateTimerEvery(int64_t interval_ms, const TimerTask& handler) {
  if (time_wheels_.empty()) {
    return 0;
  }

  std::lock_guard<std::mutex> lock(mutex_);
  ++s_inc_id;
  int64_t when = GetNowTimestamp() + interval_ms;
  GetGreatestTimeWheel()->AddTimer(std::make_shared<Timer>(s_inc_id, when, interval_ms, handler));

  return s_inc_id;
}

void TimeWheelScheduler::CancelTimer(uint32_t timer_id) {
  std::lock_guard<std::mutex> lock(mutex_);
  cancel_timer_ids_.insert(timer_id);
}

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值