C++笔试题之实现一个定时器

一.定时器(timer)的需求

1.执行定时任务的时,主线程不阻塞,所以timer必须至少持有一个线程用于执行定时任务

2.考虑到timer线程资源的合理利用,一个timer需要能够管理多个定时任务,所以timer要支持增删任务,通过容器储存任务

3.当timer空闲时(即没有任务或执行任务的时刻未到),timer中的线程不应该空转来占用资源,可通过条件变量实现

4.支持重复任务和非重复任务

二.定时器(timer)的实现

#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <mutex>
#include <thread>
#include <iostream>
#include <iomanip>
#include <sstream>

namespace CC
{
using TaskFunc = std::function<void()>;

struct Task
{
    uint64_t id;
    uint64_t period;
    bool repeated;
    TaskFunc func;
    bool removed;
    Task(uint64_t id, uint64_t period, bool repeated, TaskFunc func)
        : id(id), period(period), repeated(repeated), func(func), removed(false)
    {
    }
};

class Timer
{
public:
  Timer() : m_stop(false)
  {
      m_worker = std::thread(&Timer::run, this);
  }
  ~Timer()
  {
      m_stop.store(true);
      m_condition.notify_all();
      m_worker.join();
  }
  uint64_t add(uint64_t period_ms, bool repeated, TaskFunc func)
  {
      uint64_t when = now() + period_ms;
      Task task(m_cur_id, period_ms, repeated, func);
      {
          std::lock_guard<std::mutex> lock(m_tasks_mutex);
          m_tasks.insert({when, task});
      }
      m_condition.notify_all();
      return m_cur_id++;
  }
  // Timer::remove并没有真正的将定时任务删除,仅仅是将removed标志位设置为true,删除操作实际是在Timer::run中进行的。
  // 为什么要这么做?如果在这里如果由Timer::remove来执行m_tasks.erase(it),那么有可能删除的是Timer::run里正在执行的那个任务,这是明显不对的。
  // 所以才采用将removed标志位设置为true的这种做法。
  bool remove(uint64_t id)
  {
      bool flag = false;
      std::lock_guard<std::mutex> lock(m_tasks_mutex);
      std::multimap<uint64_t, Task>::iterator it =
          std::find_if(m_tasks.begin(), m_tasks.end(),
                       [id](const std::pair<uint64_t, Task> &item) -> bool { return item.second.id == id; });
      if (it != m_tasks.end())
      {
          it->second.removed = true;
          flag = true;
      }
      return flag;
  }

  private:
    std::thread m_worker;
    std::atomic<bool> m_stop;
    std::multimap<uint64_t, Task> m_tasks;
    std::mutex m_tasks_mutex;
    std::condition_variable m_condition;
    uint64_t m_cur_id;
    // m_condition.wait之后继续向下执行,此时如果m_stop是true,那么表明timer要被停止了,那线程也要结束,所以一个break跳出最开始的while (true)循环,让线程执行结束。
    // 如果m_stop是false,那表明现有可能有定时任务需要执行了。取出第一个任务m_tasks.begin(),也是按时间排序最靠前的任务。用任务的时刻和当前时刻对比:
    // 如果"时辰已到",那就执行。执行的之后需要注意的是,要将锁释放lock.unlock(),因为继续持有没有任何意义,反而会阻塞住对m_tasks的一些操作。
    // 如果"时辰未到",那就执行m_condition.wait_for,让当前线程休眠,直到std::chrono::milliseconds(task_time - cur_time)这段时间过去或者被唤醒。
    void run()
    {
        while (true)
        {
            std::unique_lock<std::mutex> lock(m_tasks_mutex);
            m_condition.wait(lock, [this]() -> bool { return !m_tasks.empty() || m_stop; });
            if (m_stop)
            {
                break;
            }
            uint64_t cur_time = now();
            std::multimap<uint64_t, Task>::iterator it = m_tasks.begin();
            uint64_t task_time = it->first;
            if (cur_time >= task_time)
            {
                Task &cur_task = it->second;
                if (!cur_task.removed)
                {
                    lock.unlock();
                    cur_task.func();
                    lock.lock();
                    if (cur_task.repeated && !cur_task.removed)
                    {
                        uint64_t when = cur_time + cur_task.period;
                        Task new_task(cur_task.id, cur_task.period, cur_task.repeated, cur_task.func);
                        m_tasks.insert({when, new_task});
                    }
                }
                m_tasks.erase(it);
            }
            else
            {
                m_condition.wait_for(lock, std::chrono::milliseconds(task_time - cur_time));
            }
        }
    }

    uint64_t now() //ms
    {
        auto now = std::chrono::system_clock::now();
        auto duration = now.time_since_epoch();
        return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
    }
};

} // namespace CC

// 格式化时间,精确到毫秒.
std::string getTimeString()
{
    auto now = std::chrono::system_clock::now();
    auto duration = now.time_since_epoch();
    auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();

    std::time_t time = std::chrono::system_clock::to_time_t(now);
    std::tm *tm = std::localtime(&time);

    std::stringstream ss;
    ss << std::put_time(tm, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << millis % 1000;
    return ss.str();
}

// 待执行的任务
void theTask(int id)
{
    std::cout << getTimeString() << " id = " << id << std::endl;
}

int main()
{
    CC::Timer *timer = new CC::Timer();
    timer->add(3000, false, std::bind(theTask, 1));
    uint64_t id = timer->add(2000, true, std::bind(theTask, 2));
    timer->add(1000, true, std::bind(theTask, 3));
    std::this_thread::sleep_for(std::chrono::seconds(3));

    timer->remove(id);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    delete timer;

    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}

参考链接:https://zhuanlan.zhihu.com/p/668916073

相关推荐
机器视觉知识推荐、就业指导1 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.993 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王3 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_3 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀3 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun4 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥4 小时前
c++中mystring运算符重载
开发语言·c++·算法
天若有情6735 小时前
c++框架设计展示---提高开发效率!
java·c++·算法
Root_Smile5 小时前
【C++】类和对象
开发语言·c++