C++ - TimingWheel 알고리즘을 사용한 타이머

  • 자세한 설명은 http://d2.naver.com/helloworld/267396.
  • 타임아웃 작업의 만료 여부에 대한 검사가 필요 없는 등록과 실행 두 작업만을 모두 O(1)의 시간 복잡도로 처리할 수 있는 타이머를 구현할 수 있다.

출처: https://github.com/billlin0904/librapid

uint64_t TimingWheel::add(uint32_t timeout, bool onece, TimeoutCallback callback) {
  //std::lock_guard<platform::Spinlock> guard{ lock_ };
    
  auto bucket = (nextIndex_ + timeout / tickDuration_) % buckets_.size();
  auto id = uint32_t(buckets_[bucket].size());
  
  buckets_[bucket].push_back(std::make_pair(onece, callback));
  
  Slot s;
  s.value[0] = bucket;
  s.value[1] = id;
  return s.pad;
}
void TimingWheel::onTick() {
  //std::lock_guard<platform::Spinlock> guard{ lock_ };

  auto & bucket = buckets_[nextIndex_];
  for (auto &callback : bucket) {
    if (callback.second != nullptr) {
      callback.second();
      if (callback.first) {
        callback.second = nullptr;
      }
    }
  }

  nextIndex_ = (nextIndex_ + 1) % buckets_.size();
}


전체 코드
TimingWheel.h

class Timer;

class TimingWheel;
using TimingWheelPtr = std::shared_ptr<TimingWheel>;

class TimingWheel {
public:
    using TimeoutCallback = std::function<void(void)>;

  static TimingWheelPtr createTimingWheel(uint32_t tickDuration, uint32_t maxBuckets);

  TimingWheel(uint32_t tickDuration, uint32_t maxBuckets);

    ~TimingWheel();

    TimingWheel(TimingWheel const &) = delete;
    TimingWheel& operator=(TimingWheel const &) = delete;

    void start();

  uint64_t add(uint32_t timeout, bool onece, TimeoutCallback callback);

    void remove(uint64_t slot);

private:
    void onTick();

    union Slot {
        uint64_t pad;
        uint32_t value[2];
    };

    using Bucket = std::vector<std::pair<bool, TimeoutCallback>>;

  details::Timer timer_;
    std::vector<Bucket> buckets_;
    mutable platform::Spinlock lock_;
    uint64_t maxTimeout_;
  uint32_t tickDuration_;
    uint32_t nextIndex_;
};

TimingWheel.cpp

static void defaultTimeoutCallback() {
}

TimingWheelPtr TimingWheel::createTimingWheel(uint32_t tickDuration, uint32_t maxBuckets) {
  return std::make_shared<TimingWheel>(tickDuration, maxBuckets);
}

TimingWheel::TimingWheel(uint32_t tickDuration, uint32_t maxBuckets)
    : buckets_(maxBuckets)
  , tickDuration_(tickDuration)
    , nextIndex_(0) {
  maxTimeout_ = tickDuration_ * maxBuckets;
}

TimingWheel::~TimingWheel() {
  std::lock_guard<platform::Spinlock> guard{ lock_ };
    timer_.stop();
}

void TimingWheel::start() {
  timer_.start(std::bind(&TimingWheel::onTick, this), tickDuration_);
}

uint64_t TimingWheel::add(uint32_t timeout, bool onece, TimeoutCallback callback) {
  std::lock_guard<platform::Spinlock> guard{ lock_ };
    
  RAPID_ENSURE(timeout <= maxTimeout_);

  auto bucket = (nextIndex_ + timeout / tickDuration_) % buckets_.size();
  auto id = uint32_t(buckets_[bucket].size());
    
  buckets_[bucket].push_back(std::make_pair(onece, callback));
    
  Slot s;
    s.value[0] = bucket;
    s.value[1] = id;
    return s.pad;
}

void TimingWheel::onTick() {
  std::lock_guard<platform::Spinlock> guard{ lock_ };

    auto & bucket = buckets_[nextIndex_];
    for (auto &callback : bucket) {
    if (callback.second != nullptr) {
      callback.second();
      if (callback.first) {
        callback.second = nullptr;
      }
    }
    }

    nextIndex_ = (nextIndex_ + 1) % buckets_.size();
}

void TimingWheel::remove(uint64_t slot) {
  std::lock_guard<platform::Spinlock> guard{ lock_ };

    Slot s;
    s.pad = slot;
    auto bucket = s.value[0];
    auto &chain = buckets_[bucket];
    
  RAPID_ENSURE(s.value[1] < chain.size());
    chain[s.value[1]].second = nullptr;
}


사용 예

void Connection::addReuseTimingWheel() {
  auto pConn = shared_from_this();

  pTimeWaitReuseTimer_->add(platform::TcpIpParameters::getInstance().getTcpTimedWaitDelay(), true, [pConn]() {
    RAPID_LOG_INFO() << "Background reuse socket";
    if (!pConn->hasUpdataAcceptContext_) { // ?Accept퀂퐑ㄳ?톝㏓뻤acceptAsync
      return;
    }
    try {
      pConn->acceptAsync();
    } catch (Exception const &e) {
      RAPID_LOG_FATAL() << e.what();
    }
  });
}

이 글은 2020-02-27에 작성되었습니다.