1:简单基础
定时器的核心知识点,对我来说就是获取当前时间和设置回调函数。
简单练习:
c语言通过gettimeofday 获取当前时间并进行处理
回调函数的定义(函数参数有必要适当存储) typedef void(Timerfunc)(void p);
1.1 简单源码演示
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h> //sleep
typedef unsigned long int uint64_t;
static uint64_t GetCurrentTime()
{struct timeval tv;gettimeofday(&tv, NULL);return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}void callback(void * p)
{int * i = (int *)p;printf("callback %d\n", *i);
}int main()
{uint64_t mytest = 1;printf("%lu \n", GetCurrentTime());sleep(2);struct timeval tv;gettimeofday(&tv, NULL);printf("second: %ld\n", tv.tv_sec); // 秒printf("millisecond: %ld\n", tv.tv_sec * 1000 + tv.tv_usec / 1000); // 毫秒printf("microsecond: %ld\n", tv.tv_sec * 1000000 + tv.tv_usec); // 徽秒sleep(3); // 让程序休眠3秒printf("---------------------sleep 3 second-------------------\n");gettimeofday(&tv, NULL);printf("second: %ld\n", tv.tv_sec); // 秒printf("millisecond: %ld\n", tv.tv_sec * 1000 + tv.tv_usec / 1000); // 毫秒printf("microsecond: %ld\n", tv.tv_sec * 1000000 + tv.tv_usec); // 徽秒
//回调函数的简单定义和使用typedef void(*Timerfunc)(void* p);int func_para = 3;Timerfunc m_func = callback;void * para = (void*)&func_para;(*m_func)(para);return 0;
}
1.2 :运行结果
root@aliy:/home/leetcode# ./get_time1
1739506868441
second: 1739506870
millisecond: 1739506870441
microsecond: 1739506870441927
---------------------sleep 3 second-------------------
second: 1739506873
millisecond: 1739506873442
microsecond: 1739506873442060
callback 3
2:借助已有的stl容器实现是最方便的
不知道哪里参考的一个代码,借助了stl中的一个优先级队列,就简单整理一下吧。
回顾好久没写的细节:
0:优先级队列 priority_queue 支持大堆小堆 (自己定义比较函数)
1:锁和条件变量 条件变量的几种信号等待方式。
2:chrono下的相关获取时间的接口需要梳理一下。
3:std::function 和lamba需要回顾练习一下
4:stl的push时可以直接构造结构体对象,task_queue.push(Task{func, exec_time});
5:条件变量中的wait 以及wait_until
====》 已经有唤醒 wait用条件等待 防止虚假唤醒
====》wait_until 接口可以实现等待到特定时间后进行执行(系统调用内部定时器实现? 会虚假唤醒吗?)。 和自己代码实现时间差同功能
2.1:练习源码
都是C++11的东东 需要回顾。
//定时器的简单实现 借助stl容器,使用线程进行专门的定时器处理。//容器中保存了定时器的超时时间,以及对应的回调函数
#include <stdio.h>
#include <iostream>
#include <functional>
#include <mutex>
#include <thread>
#include <chrono>
#include <vector>
#include <condition_variable>
#include <atomic>
#include <queue>class Timer{
private:std::thread worker;std::atomic<bool> stop;//锁和条件变量std::mutex queue_mutex;std::condition_variable condition;struct Task{std::function<void(void)> func;//std::chrono::steady_clock 只能增加的单调时钟 std::chrono::time_point表示某一刻的对象//这里时间的相关接口需要参考chronostd::chrono::time_point<std::chrono::steady_clock> exec_time; //为了给wait_until做参数 直接指定bool operator >(const Task & other) const{return exec_time > other.exec_time;}};//优先队列 用task为元素类型 以std::vector<Task> 进行存储 按照默认的比较函数进行比较 实现最小堆std::priority_queue<Task, std::vector<Task>, std::greater<Task>> task_queue; //底层是堆的结构private:void run(){while(!stop){//这里进行死循环 或者加锁条件变量实现队列中任务的提取if(task_queue.empty()){std::unique_lock<std::mutex> lock(queue_mutex);//防止虚假唤醒condition.wait(lock, [this] {return !this->task_queue.empty() || this->stop;});//这里无法访问类的成员变量 如何函数内部或者全局变量 即可以// condition.wait(lock, [] {// return !task_queue.empty() || stop;// });} if(task_queue.empty() || stop){return;}{// auto now = std::chrono::steady_clock::now();auto exec_task_time = task_queue.top().exec_time; //目标执行的时间std::unique_lock<std::mutex> lock(queue_mutex);//注意第二个参数 是当前时间加上最大等待时间 if(condition.wait_until(lock, exec_task_time) == std::cv_status::timeout){auto task = task_queue.top();task_queue.pop();lock.unlock();task.func(); //这里没有定义参数 可以定义参数为自己}}}}public:Timer():stop(false), worker(&Timer::run, this){}~Timer(){ //单例时才把构造函数析构函数设置为私有stop = true;condition.notify_all();worker.join();}static inline time_t get_Clock(){// 获取当前时间点auto now = std::chrono::steady_clock::now();// 获取从纪元到现在所经过的持续时间auto duration = now.time_since_epoch();//转换为毫秒并返回return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();}void schedule(const std::function<void()> &func, int delay_ms){auto exec_time = std::chrono::steady_clock::now() +std::chrono::milliseconds(delay_ms);{std::unique_lock<std::mutex> lock(queue_mutex);task_queue.push(Task{func, exec_time}); //注意这里的细节}condition.notify_all();}
};int main()
{Timer timer;printf("now = %lu \n", Timer::get_Clock());timer.schedule([](){printf("exec 1000 %lu \n", Timer::get_Clock());}, 1000);timer.schedule([](){printf("exec 3000 %lu \n", Timer::get_Clock());}, 3000);std::this_thread::sleep_for(std::chrono::seconds(5));return 0;
}
2.2 :运行结果
oot@aliy:/home/leetcode# g++ my_timer1.c -o my_timer -std=c++11
root@aliy:/home/leetcode# ./my_timer
now = 16306525752
exec 1000 16306526753
exec 3000 16306528753
3:总结一些其他遗留
定时器的实现中,往往需要数据结构配合。(时间戳和回调 需要支持排序 需要方便插入)
1:红黑树存储数据结构 比如set map mutilset mutilmap 以及nginx下封装的红黑树。
2:使用最小堆进行存储。
3:跳表(有序,可以快速插入 参考redis中的跳表源码)/时间轮定时器。
3.1:时间轮定时器
3.2:一个来自别人的demo代码练习
std::vector 模拟数据结构
这样设计有最大超时时间限制吧,然后轮询也有精度。
存储的是节点指针,增加引用计数实现多次执行,类型心跳
//时间轮定时器 采用(数组+链表)链表结合vector的方式存储数据结构 采用轮询的方式处理事件#include <unistd.h>
#include <iostream>
#include <vector>
#include <list>
using namespace std;#include <sys/time.h>//同一个指针对象 多次加入只是引用计数增加 执行次数增加。
class CTimerNode {
public:CTimerNode(int fd) : id(fd), ref(0) {}void Offline() {this->ref = 0;}//通过引用计数的方式 确定是否销毁该对象 加入时++ 消费时-- //可能多次加入定时器 bool TryKill() {if (this->ref == 0) return true;DecrRef();if (this->ref == 0) {cout << id << " is killed down" << endl;return true;}cout << id << " ref is " << ref << endl;return false;}void IncrRef() {this->ref++;}protected:void DecrRef() {this->ref--;}private:int ref;int id;
};const int TW_SIZE = 16;
const int EXPIRE = 10;
const int TW_MASK = TW_SIZE - 1;
static size_t iRealTick = 0;
//链表的节点
typedef list<CTimerNode*> TimeList;
typedef TimeList::iterator TimeListIter;
//用vector+list 构造时间轮数据结构
typedef vector<TimeList> TimeWheel;void AddTimeout(TimeWheel &tw, CTimerNode *p) {if (p) {p->IncrRef();TimeList &le = tw[(iRealTick+EXPIRE) & TW_MASK]; //基于当前的时间 放入对应的list中 le.push_back(p);}
}// 用来表示delay时间后调用
void AddTimeoutDelay(TimeWheel &tw, CTimerNode *p, size_t delay) {if (p) {p->IncrRef();TimeList &le = tw[(iRealTick+EXPIRE+delay) & TW_MASK];le.push_back(p);}
}//命中
void TimerShift(TimeWheel &tw)
{size_t tick = iRealTick;iRealTick++;TimeList &le = tw[tick & TW_MASK]; //每次向前走一个//循环遍历轮子 消费轮子中的第一个节点对应的list中的所有事件TimeListIter iter = le.begin();for (; iter != le.end();iter++) {CTimerNode *p = *iter;if (p && p->TryKill()) {delete p;}}le.clear();
}static time_t current_time() {time_t t;struct timeval tv;gettimeofday(&tv, NULL);t = (time_t)tv.tv_sec;return t; //这里返回的是秒
}int main ()
{TimeWheel tw(TW_SIZE);CTimerNode *p = new CTimerNode(10001);AddTimeout(tw, p); //加入时间轮定时器中AddTimeoutDelay(tw, p, 5); //对象已经存在 5s后执行对应的回调time_t start = current_time();for (;;) {time_t now = current_time();//这里以秒为单位 进行依次命中轮询if (now - start > 0) {for (int i=0; i<now-start; i++)TimerShift(tw);start = now;cout << "check timer shift " << iRealTick << endl;}usleep(2500); //2500 微妙 =2.5ms}return 0;
}
3.3 :demo运行
同一个TimerNode节点,只是把指针加入了时间轮中。
每次处理节点时根据引用计数进行判断了。
root@aliy:/home/leetcode# ./wheel_timer
check timer shift 1
check timer shift 2
check timer shift 3
check timer shift 4
check timer shift 5
check timer shift 6
check timer shift 7
check timer shift 8
check timer shift 9
check timer shift 10
10001 ref is 1
check timer shift 11
check timer shift 12
check timer shift 13
check timer shift 14
check timer shift 15
10001 is killed down
3.4:更复杂的时间轮
linux内核中使用比较复杂的时间轮来进行定时器的处理
参考时钟的时针 分针 秒针,多个类似上面的时间轮进行配合,采用不同的精度,配合实现更复杂的功能(只有第一层消费,后面的基层都是按层移动到上一层)。