1 概述

很多情况下我们需要在程序中定时去执行一些任务,比如定时发送心跳,定时计数,RPC定时超时唤醒等等,我们需要一个定时器来帮我们做这件事情,这个定时器需要按照我们所规定的时间定时唤醒去执行我们所赋给他的任务。那么对于一个定时器我们希望他准确且高效。
实现一个定时器主要有三个要素:唤醒装置、计时方式、元素存放的数据结构唤醒装置:不过无论使用什么方式进行定时唤醒,定时器都是存在误差的,这个误差来源于系统调用、代码逻辑等,也就是说一个定时器并不能真正做到精准的定时唤醒,不过只要这个误差在可接受范围内,那么我们默认他是正确的。通常定时器使用的唤醒装置有pselect、epoll_wait、condition_variable等等,这些都属于系统调用范畴,系统调用需要时间,pselect测试过系统调用本身在几十微秒的级别、epoll_wait应该也差不了多少,不过epoll_wait的输入时间是毫秒级别的,condition_variable和pselect输入级别可以达到纳秒级别,不过在用户态实现纳秒级定时器应该不可能。计时方式:定时器时间通常使用单调递增的时间,C++11中提供了std::steady_clock这种单调递增时间,配合C++11的条件变量很好用,通常不会使用墙上时钟作为定时器的计时,因为墙上时钟可以通过修改系统时间进行修改,如果修改了系统时间,会对定时器内的元素产生未定义的行为。 数据结构:实现一个定时器较常使用的数据结构是最小堆,当然可以使用链表、multimap等。针对应用场景不同可以实现不同的定时器策略,如果这个定时器所存放的元素较少,应用场景竞争压力不大,那么使用一个线程加一个最小堆,可以很方便的实现。

2 brpc定时器的实现原理

brpc是百度研发的开源rpc框架,性能甚佳,适合高并发高性能的开发场景,rpc框架必然需要使用定时器,因为一个RPC请求是有超时时间的,虽然RPC超时相对并不那么常发生。brpc内部使用bthread这种支持work stealing工作方式的协程,同时也支持pthread的方式,内部多线程并发处理用户请求。对于这样一个rpc框架,他所需要的定时器必须要满足:准确、低竞争高并发的要求。
brpc采用condition_variable的唤醒方式+墙上时钟+小顶堆的方式实现其定时器。在多线程场景中用户启动一个定时器通常通过对小顶堆加锁然后插入,这种多生产者单消费者的方式加锁解锁对于大并发的场景是非常敏感的,锁竞争较大的情况下抢锁操作很容易成为性能热点。通常降低竞争提高并发的方式可以通过做partition的方式,当然也可以引入无锁队列等方式,无锁队列并不是一定会比有锁队列快,而且无锁队列的正确性需要较为全面的测试来保证。
brpc通过partition的方式降低竞争,brpc内部为定时器单独启动一个线程Timerthread, Timerthread负责执行定时器唤醒并执行定时任务,为了避免多线程争抢,Timerthread内部将定时器元素拆分到多个bucket,每个bucket有自己的锁,bucket内部以链表的形式组织定时事件,虽然这还是一种多生产者单消费者模型,但是在总体上将插入定时事件的竞争降低到了1/bucket_num。定时事件通过调用Timerthread的schedule接口被插入,同时携带者其定时长度及回调函数,定时事件hash到对应bucket是以调用者的线程id与bucket的数量取余,大概率上可以达到均衡。
Timerthread的线程函数负责从这些bucket中取出,然后将定时器超时的事件逐个执行其回调函数。
brpc的定时器时间使用的是墙上时钟,这会存在问题,其开源的文档中也提到了这点。另外一点,所有超时的定时事件回调都在Timerthread中串行调用,如果回调过多,或者回调本身较为耗时,那么会影响后续定时事件被调度的时间。

3 brpc定时器源码

Timerthread:

 class TimerThread {public:...// 启动定时器线程int start(const TimerThreadOptions* options);​// 等待定时器线程退出void stop_and_join();​// 将fn, arg, asbtime封装成定时器事件push到对应的bucketTaskId schedule(void (*fn)(void*), void* arg, const timespec& abstime);...private:// 线程函数void run();// 当前定时器所有的bucketsBucket* _buckets;        // list of tasks to be run​internal::FastPthreadMutex _mutex;    // protect _nearest_run_time// _nearest_run_time代表当前定时器中最近的需要被调度的事件时间点int64_t _nearest_run_time;// 用于触发condition variable等待的信号int _nsignals;// pthread定时器自己的线程pthread_t _thread;       // all scheduled task will be run on this thread};

Bucket

 // bucket cache对齐,防止false sharingclass BAIDU_CACHELINE_ALIGNMENT TimerThread::Bucket {public:...// 外围timerthread会调用bucket的schedule将事件插入其链表// bucket并不为定时事件排序,也没必要排序,所有的排序都是在// timerthread的线程函数中统一进行堆排序ScheduleResult schedule(void (*fn)(void*), void* arg,const timespec& abstime);​// timerthread的线程函数中会通过该函数获取当前bucket的所有事件Task* consume_tasks();​private:internal::FastPthreadMutex _mutex;// 每个bucket有一个自己的记录,表示自己当前所存储的所有定时事件中最近要执行的时间点int64_t _nearest_run_time;// 链表头部Task* _task_head;};

线程函数

// timerthread的线程函数,负责从bucket取出定时事件并执行定时事件的回调void TimerThread::run() {run_worker_startfn();int64_t last_sleep_time = butil::gettimeofday_us();BT_VLOG << "Started TimerThread=" << pthread_self();​// 存放事件的容器,使用std::push/pop_heap对其进行堆排序std::vector<Task*> tasks;tasks.reserve(4096);...while (!_stop.load(butil::memory_order_relaxed)) {...// 从所有的bucket中取出定时事件for (size_t i = 0; i < _options.num_buckets; ++i) {Bucket& bucket = _buckets[i];for (Task* p = bucket.consume_tasks(); p != nullptr; ++nscheduled) {// p->next should be kept first// in case of the deletion of Task p which is unscheduledTask* next_task = p->next;​if (!p->try_delete()) { // remove the task if it's unscheduled// 将定时事件push到容器然后排序tasks.push_back(p);// 进行堆排序,排序依据task_greaterstd::push_heap(tasks.begin(), tasks.end(), task_greater);}p = next_task;}}​bool pull_again = false;while (!tasks.empty()) {...// 将堆顶弹出std::pop_heap(tasks.begin(), tasks.end(), task_greater);tasks.pop_back();// 执行堆顶的定时事件回调,这里所有的定时事件回调串行执行if (task1->run_and_delete()) {++ntriggered;}}if (pull_again) {BT_VLOG << "pull again, tasks=" << tasks.size();continue;}​...timespec* ptimeout = NULL;timespec next_timeout = { 0, 0 };const int64_t now = butil::gettimeofday_us();if (next_run_time != std::numeric_limits<int64_t>::max()) {// 计算需要等待的下一个最小时间点next_timeout = butil::microseconds_to_timespec(next_run_time - now);ptimeout = &next_timeout;}busy_seconds += (now - last_sleep_time) / 1000000.0;// 条件变量等待计算好的时间futex_wait_private(&_nsignals, expected_nsignals, ptimeout);last_sleep_time = butil::gettimeofday_us();}}

定时器的实现原理 不消耗cpu_brpc定时器实现原理相关推荐

  1. 单片机原理及其应用——单片机定时器中断实验(八段数码管依次显示0~9数字)

    目录 一.实验要求 二.知识要点 (一)单片机定时器/计数器 (二)相关的特殊功能寄存器 1.工作方式寄存器TMOD 2.定时器/计数器控制寄存器TCON (三)定时器/计数器的工作方式 1.工作方式 ...

  2. 单片机原理与应用实验——定时器(C语言),用定时器设计延时函数,并实现流水灯的功能

    (基于51核的STC12C5A60S2,keil uvision5 mdk 5.25 编译) #include <STC12C5A60S2.h>//选择对应的代码进行测试 1--运行,0- ...

  3. MSP430F169 Timer_A原理(一)----定时器的四个模式

    Timer_A简介 Timer_A是程序设计的核心,它有一个十六位定时器和多路比较/捕获通道组成.每一个比较/捕获通道都以十六位定时器的定时功能为核心进行单独的控制. Timer_A特点 1.具有四种 ...

  4. c语言定时器定时1ms程序,STM32 Cubemx 配置定时器定时1mS

    最近才发现原来我把定时器里的配置参数代表的意义给搞混了,这里记录一下,防止以后自己忘记. 以建立一个定时1mS定时器为例: 1.先打开定时器 2.配置好时钟 3.配置定时器设置 重点来了,以前在这里我 ...

  5. 网络编程 高性能定时器数据结构分析 | 时间轮 红黑树定时器性能分析 | 为什么要做用户态定时器

    为什么要用户态的定时器? 首先是为什么要做定时器,定时器的主要说的是我们的应用(业务?功能?总之有这个需求)要做一个定时的任务.其实如果不想为什么,好像是理所当然的.我写这个的时候,知乎有一个问题(L ...

  6. 【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )

    文章目录 一.Timer 定时器基本使用 二.Timer 定时器常用用法 三.Timer 源码分析 四.Timer 部分源码注释 五.源码及资源下载 参考文档 : Timer 定时器 API 文档 T ...

  7. linux定时器回调处理过程,Linux内核系统定时器TIMER实现过程分析

    可见涉及到系统定时器的数据结构并不多,那么:对于一个linux系统中,定时器个数可能会很多,而且每个定时器的超时事件时间并不相同,所以如何管理和处理定时器超时事件,关系到内核性能的高低.它根据不同的定 ...

  8. 解决在JS中阻止定时器“重复”开启问题、Vue中定时器的使用

    1.问题描述 在一些需求开发中.需要设定软件提供服务的时间段(营业时间).这时可以选择定时器来实现.可以选择让定时器每隔一段时间检测当前时间是否在服务时间.到达服务时间.进入服务状态.未到服务时间.进 ...

  9. java web定时器_java的web项目中使用定时器 | 学步园

    之前接触过程序中的定时任务,但是没去自己亲自尝试过.终于这次抽空搞了一下.(一定要自己去操作,才能长经验,光看到过是没用的) 以下是两种方法,我使用的是监听的方法. JAVA WEB定时器,定时器的启 ...

最新文章

  1. 推出第一个免费工具CCT
  2. 机器学习(MACHINE LEARNING)MATLAB三层神经网络的简单应用
  3. CocosCreate粒子系统白边问题
  4. web自动化之鼠标事件
  5. mybais传入多个参数的方法
  6. python读取xlsx文件pandas_python-如何使用iPython中的pandas库读取.xlsx文件?
  7. Linux+apache+svn
  8. MyBatis(四)MyBatis插件原理
  9. SAP CRM和Cloud for Customer的UI界面皮肤更改
  10. Web安全-伪静态网页
  11. 使用七牛图片遇到的图片方向翻转问题
  12. 基于fitter库判断已知数据服从分布——python
  13. oracle数据库无法查询,关于oracle中无法查询中文条件的解决办法
  14. idea中git的运用
  15. [转载] C++子字符串查找及提取
  16. ubuntu16.04装机7:安装VScode
  17. Javascript:利用JS在空白网页中绘制简单图形
  18. 转换BIM IFC数据为CityGML
  19. echarts全国地图显示到城市级别
  20. 使用 Vite + 前端框架 (SolidJs,React,Svelte,Vue) 来开发 油猴脚本

热门文章

  1. jsp ---- filter
  2. nginx虚拟目录支持PHP,nginx设置虚拟目录
  3. android9 mate10,华为仅这四部手机升到安卓9.0,Mate10和P20用户窃喜!
  4. alert 返回页面 刷新_页面生命周期:DOMContentLoaded,load,beforeunload,unload
  5. seo自动工具_【SEO工具】搭建一个网站需要用到哪些SEO工具?
  6. JAVA打印变量类型
  7. Android开发笔记(一百七十一)使用Glide加载网络图片
  8. Android开发笔记(一百一十四)发布工具
  9. 拥有中国最深基坑的超高层建筑完成地下连续墙施工
  10. 50.本地VMware环境虚拟机的异地(Azure)容灾(上)