Linux下C++实现一个定时器

要在Linux下实现一个定时器,可以使用以下两种方法:

使用系统提供的定时器API

Linux系统提供了一些定时器API,如setitimer、timer_create、timer_gettime等,可以使用这些API来实现定时器。以setitimer为例,可以按照以下步骤来使用:
1.定义一个itimerval结构体变量,该结构体包含定时器的初始值和定时器到期后的动作。
2.使用setitimer函数启动定时器,该函数接受一个定时器类型参数和一个itimerval结构体参数。
3.通过SIGALRM信号来处理定时器到期后的动作,需要安装一个SIGALRM信号的处理函数。
代码如下:

#pragma once
#include <sys/timerfd.h>

#include <atomic>
#include <chrono>
#include <ctime>
#include <functional>
#include <thread>

namespace pickup {

class Timer {
 public:
  using TimerfdCallBack = std::function<void()>;
  /**
   * Create and start a timer
   * @param timeout  Timeout in milliseconds before @p cb is called, zero disarms timer
   * @param period   For periodic timers this is the period time that @p timeout is reset to
   * @param cb       Callback function
   *
   * Note, the @p timeout value must be non-zero.  Setting it to zero
   * disarms the timer.  This is the behavior of the underlying Linux
   * function [timerfd_settimer(2)](https://man7.org/linux/man-pages/man2/timerfd_settime.2.html)
   *
   */
  Timer(int timeout, int period, TimerfdCallBack&& cb);
  ~Timer();

  void start();
  void stop();
  bool isRunning() const { return is_running_; }

 private:
  int createTimerfd();
  bool setTimer(int timeout, int period);
  void threadFunc();
  void handleRead();

 private:
  // 定时器的起始时间
  int timeout_;
  // 定时器的间隔时间
  int period_;
  // 定时器文件描述符
  int timerfd_;
  // 回调函数
  TimerfdCallBack callback_;
  // 定时器工作线程
  std::thread thread_;
  // 是否运行中
  std::atomic<bool> is_running_;
};

};  // namespace pickup

#include "pickup/timer/Timer.h"

#include <errno.h>
#include <string.h>
#include <sys/poll.h>
#include <unistd.h>

namespace pickup {

static void msec2tspec(int msec, struct timespec* ts) {
  if (msec > 0) {
    ts->tv_sec = msec / 1000;
    ts->tv_nsec = (msec % 1000) * 1000000;
  } else {
    ts->tv_sec = 0;
    ts->tv_nsec = 0;
  }
}

Timer::Timer(int timeout, int period, TimerfdCallBack&& cb)
    : timeout_(timeout), period_(period), timerfd_(createTimerfd()), callback_(cb), is_running_(false) {}

Timer::~Timer() {
  close(timerfd_);
  timerfd_ = -1;
}

void Timer::start() {
  if (is_running_) return;
  if (!setTimer(timeout_, period_)) return;

  is_running_ = true;
  thread_ = std::thread(&Timer::threadFunc, this);
}

void Timer::stop() {
  if (!is_running_) return;
  is_running_ = false;
  if (thread_.joinable()) {
    thread_.join();
  }
}

void Timer::threadFunc() {
  int timeout = 5000;  // 超时时间(单位:毫秒)
  struct pollfd pfd;
  bzero(&pfd, sizeof(pfd));
  pfd.fd = timerfd_;
  pfd.events = POLLIN;
  while (is_running_) {
    int nready = poll(&pfd, 1, timeout);
    if (nready < 0) {
      if (errno == EINTR) {
        continue;
      } else {
        perror("poll() failed");
        break;
      }
    } else if (0 == nready) {
      printf("poll() timed out.\n");
      continue;
    } else {
      if (pfd.events & POLLIN) {
        // 先读取fd中的数据
        handleRead();
        // 再调用回调函数
        callback_();
      }
    }
  }
}

int Timer::createTimerfd() {
  int timerfd = timerfd_create(CLOCK_REALTIME, 0);
  if (-1 == timerfd) {
    perror("timerfd_create");
  }

  return timerfd;
}

bool Timer::setTimer(int timeout, int period) {
  struct itimerspec time;
  msec2tspec(timeout, &time.it_value);
  msec2tspec(period, &time.it_interval);
  int ret = timerfd_settime(timerfd_, 0, &time, NULL);
  if (-1 == ret) {
    perror("timerfd_settime");
    return false;
  }

  return true;
}

void Timer::handleRead() {
  uint64_t expirations = 0;
  ssize_t result = read(timerfd_, &expirations, sizeof(expirations));
  if (result == -1) {
    perror("timerfd read");
  }
}

}  // namespace pickup

使用C++11提供的std::thread和std::chrono库

C++11提供了std::thread和std::chrono库,可以使用这些库来实现定时器。具体步骤如下:
1.创建一个std::thread对象,该对象将执行一个定时器函数。
2.在定时器函数中,使用std::chrono::steady_clock来获取当前时间和定时器开始时间的差值,判断是否到达定时器到期时间。
3.如果到达定时器到期时间,则执行定时器动作,否则继续等待。
需要注意的是,这种方法需要在定时器函数中使用while循环来不断检测定时器是否到期,可能会对系统资源造成一定的负担。

posted @ 2023-05-29 16:45  卡尔的思索  阅读(763)  评论(0编辑  收藏  举报