一.定时器(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;
}