定时器_定时器中断 - CSDN
定时器 订阅
1876年,英国外科医生索加取得一项定时装置的专利,用来控制煤气街灯的开关。它利用机械钟带动开关来控制煤气阀门。起初每周上一次发条,1918年使用电钟计时后,就不用上发条了。随着生活水平的提高,定时器的用途也越来越广泛。比如对开水机、热水器的定时控制,有了大功率定时器,定时开机、定时关机,实现节能、安全、健康的使用。 展开全文
1876年,英国外科医生索加取得一项定时装置的专利,用来控制煤气街灯的开关。它利用机械钟带动开关来控制煤气阀门。起初每周上一次发条,1918年使用电钟计时后,就不用上发条了。随着生活水平的提高,定时器的用途也越来越广泛。比如对开水机、热水器的定时控制,有了大功率定时器,定时开机、定时关机,实现节能、安全、健康的使用。
信息
外文名
The timer
作    用
计时
中文名
定时器
性    质
科学用品
定时器概述
定时器(Timer)人类最早使用的定时工具是沙漏或水漏,但在钟表诞生发展成熟之后,人们开始尝试使用这种全新的计时工具来改进定时器,达到准确控制时间的目的。定时器确实是一项了不起的发明,使相当多需要人控制时间的工作变得简单了许多。人们甚至将定时器用在了军事方面,制成了定时炸弹,定时雷管。不少家用电器都安装了定时器来控制开关或工作时间。
收起全文
精华内容
参与话题
  • 定时器

    千次阅读 2019-04-08 17:23:02
    1.设置定时器 1.1setTimeout() 作用:到达间隔时间之后,只调用一次回调函数 **语法: ** window.setTimeout(回调函数, 间隔时间) window可以省略 间隔时间以毫秒为单位 返回这个定时器的标识符,是数字类型 ...

    1.设置定时器

      1.1setTimeout()

    作用:到达间隔时间之后,只调用一次回调函数

    **语法: ** window.setTimeout(回调函数, 间隔时间)

    • window可以省略
    • 间隔时间以毫秒为单位
    • 返回这个定时器的标识符,是数字类型

    javascript // 创建一个定时器,1秒后执行 // timerId指向这个定时器的标识符 var timerId = setTimeout(function () { console.log('Hello World'); }, 1000);

       2.1 setInterval()

    作用: 每隔一个间隔时间,就调用一次回调函数

    **语法: ** window.setInterval(回调函数, 间隔时间)

    • window可以省略
    • 间隔时间以毫秒为单位
    • 返回这个定时器的唯一标示符,是数字类型

    javascript // 创建一个定时器,每隔1秒调用一次 //timerId 指向这个定时器的标识符 var timerId = setInterval(function () { var date = new Date(); console.log(date.toLocaleTimeString()); }, 1000);

    2.清除定时器

    6.2.1 clearTimeout()

    **作用: **清除以setTimeout方法设置的定时器

    语法: widnow.clearTimeout(定时器的标识符) window可以忽略

    6.2.2 clearInterval()

    **作用: **清除以setInterval方法设置的定时器

    语法: widnow.clearInterval(定时器的标识符) window可以忽略

    小结:

    • 设置定时器 setTimeout 和 setInterval
    • 清除定时器 clearTimeout 和 clearInterval
    展开全文
  • 51单片机定时器的原理与使用

    万次阅读 多人点赞 2017-06-18 21:04:40
    文章分析了定时器原理与定时器中断,通过实验详细阐述了定时器的使用,尤其是深入分析了各个细节问题。

    定时器是单片机的重要功能模块之一,在检测、控制领域有广泛应用。定时器常用作定时时钟,以实现定时检测,定时响应、定时控制,并且可以产生ms宽的脉冲信号,驱动步进电机。定时和计数的最终功能都是通过计数实现,若计数的事件源是周期固定的脉冲则可实现定时功能,否则只能实现计数功能。因此可以将定时和计数功能全由一个部件实现。通过下图可以简单分析定时器的结构与工作原理。

    一、定时器

    1、51单片机计数器的脉冲输入脚。主要的脉冲输入脚有Px,y, 也指对应T0的P3.4和对应T1的P3.5,主要用来检测片外来的脉冲。而引脚18和19则对应着晶振的输入脉冲,脉冲的频率和周期为

    F = f/12 = 11.0592M/12 = 0.9216MHZ      T = 1/F = 1.085us 

    2、定时器有两种工作模式,分别为计数模式和定时模式。对Px,y的输入脉冲进行计数为计数模式。定时模式,则是对MCU的主时钟经过12分频后计数。因为主时钟是相对稳定的,所以可以通过计数值推算出计数所经过的时间。

    3、51计数器的计数值存放于特殊功能寄存器中。T0(TL0-0x8A, TH0-0x8C), T1(TL1-0x8B, TH1-0x8D)

    4、TLx与THx之间的搭配关系

    1)、TLx与THx之间32进制。即当TLx计到32个脉冲时,TLx归0同时THx进1。这也称为方式0。

            2)、TLx与THx之间256进制。即当TLx计到256个脉冲时,TLx归0同时THx进1。这也称为方式1。在方式1时,最多计65536个脉冲产生溢出。在主频为11.0592M时,每计一个脉冲为1.085us,所以溢出一次的时间为1.085usx65536=71.1ms。

    3)、THx用于存放TLx溢出后,TLx下次计数的起点。这也称为方式2。

    4)、THx与TLx分别独立对自己的输入脉冲计数。这也称为方式3。

    5、定时器初始化

    1)、确定定时器的计数模式。

    2)、确定TLx与THx之间的搭配关系。

    3)、确定计数起点值。即TLx与THx的初值。

    4)、是否开始计数。TRx

    (1)和(2)可以由工作方式寄存器TMOD来设定,TMOD用于设置定时/计数器的工作方式,低四位用于T0,高四位用于T1。其格式如下:


    GATE:门控位,用于设置计数器计数与否,是否受P3.2或P3.3电压状态的影响。GATE=0时,表示计数器计数与否与两端口电压状态无关;GATA=1时,计数器是否计数要参考引脚的状态,即P3.2为高时T0才计数,P3.3为高时T1才计数。
    C/T:定时/计数模式选择位。      =0为定时模式;    =1为计数模式。
    M1M0:工作方式设置位。定时/计数器有四种工作方式,由M1M0进行设置。


    6、计数器的溢出

    计数器溢出后,THx与TLx都归0。并将特殊功能区中对应的溢出标志位TFx写为1。

    好了,理论就讲述到这。现在我们通过一些实验来看看怎么使用定时器。

    实验一、P1口连接的8个LED灯以1秒钟的频率闪烁。

    首先上代码:

    #include "reg51.h"
    char c;
    
    void Timer0_Init() //初始化定时器
    {
       TMOD = 0x01;		//
       TH0 = 0;
       TL0 = 0;	  //定时器的计数起点为0
       TR0 = 1;	//启动定时器0
    }
    
    void main()
    {
    	Timer0_Init();
    	while(1)
    	{
    		if(TF0 == 1) //检测定时器0是否溢出,每到65535次
    		{
    			TF0=0;
    			c++;
    			if(c==14)	 //71ms乘以14为1s
    			{
    				c=0;
    				P1=~P1;
    			}
    		}
    	}
    }
    上述代码的思路是每计算14个溢出,则翻转P1口状态。产生一个溢出的时间是71.1ms,14个则约为1s。


    实验二、让一个LED灯每1秒钟闪烁。

    #include "reg51.h"
    sbit LD1 = P1^0;
    
    void Timer0_Init() //初始化定时器
    {
       TMOD = 0x01;		//
       TH0 = 0;
       TL0 = 0;	  //定时器的计数起点为0
       TR0 = 1;	//启动定时器0
    }
    
    void Timer0_Overflow()	//处理定时器0的溢出事件
    {
    	static char c;
    	if(TF0 == 1) //检测定时器0是否溢出,每到65535次
    		{
    			TF0=0;
    			c++;
    			if(c==14)	 //71ms乘以14为1s
    			{
    				c=0;
    				LD1=!LD1;
    			}
    		}	
    }
    
    void main()
    {
    	Timer0_Init();	  //初始化定时器0
    	while(1)
    	{
    		Timer0_Overflow();
    	}
    }
    相比于上个例子,这里有两个区别,首先是将timer0的溢出事件作为子函数单独出来,其次是注意翻转一个led灯时候用的是“!”。

    例子三、让连接到P1口的LED1和LED8灯每1秒钟闪烁。

    #include "reg51.h"
    
    void Timer0_Init() //初始化定时器
    
    {
       TMOD |= 0x01;		//定时器0方式1,计数与否不受P3.2的影响
       TH0 = 0;
       TL0 = 0;	  //定时器的计数起点为0
       TR0 = 1;	//启动定时器0
    }
    
    void Timer0_Overflow()	//´处理定时器0的溢出事件
    {
    	static char c;
    	if(TF0 == 1) //检测定时器0是否溢出,每到65535次
    		{
    			TF0=0;
    			c++;
    			if(c==14)	 //71ms乘以14为1s
    			{
    				c=0;
    				P1 ^= (1<<0);//LD1=!LD1;
    			}
    		}	
    }
    
    void Timer1_Init()
    {
    	TMOD|=0x10; //定时器1方式1,计数与否不受P3.3的影响
    	TH1=0;
    	TL1=0; //定时器1的计数起点为0
    	TR1=1; //启动定时器1
    }
    
    void Timer1_Overflow()	//处理定时器1的溢出事件
    {
    	static char c;
    	if(TF1==1) //软件查询,主循环每跑完一圈才会到这里。
    	{
    		TF1=0;
    		c++;
    		if(c==14)
    		{
    			c=0;
    			P1 ^= (1<<7);//LD8=!LD8;
    		}
    	}	
    }
    
    void main()
    {
    	Timer0_Init();	  //初始化定时器0
    	Timer1_Init(); 	   //初始化定时器1
    	while(1)
    	{
    		Timer0_Overflow();
    		Timer1_Overflow();
    	}
    }
    相较于例二,例子三有几个点值得注意:

    1、TMOD初始化为什么采用TMOD |= 0x01或0x10的形式?

          首先如果在定时器初始化函数中采用TMOD = 0x01和TMOD = 0x10,那么将造成LD1闪烁比LD8闪烁快8倍。分析一下,从main函数开始执行,先是初始化timer0,这时候定时器1设置为工作方式1。接着程序执行到Timer1_Init(),这时候TMOD=00010000,即选定了timer1在工作方式1,但同时timer0重新配置为工作方式0, 也就是32进制,所以产生快8倍现象。

         那为什么用|这个符号就可以做到互不影响呢?|是或运算符,即有1出1,全0出0。什么意思呢?举个例子,a是11110000,b是10101010,那么a|b就是11111010。通过引入这个符号,可以实现tmod对两个定时器的独立操作。


    2、为什么使用P1 ^= (1<<0)可以实现LD1的控制呢?

          首先解释下^这个符号。^称为异或运算符,相同出0,不同出1。举个例子,a是11110000,b是10101010,那么a^b就是01011010。

          然后再来分析 x ^= (1<<i), 假设x为10101010,当i为1时, (1<<i)为00000010,那么x^ (1<<i)=10101010^00000010=10101000。当i为2时,(1<<i)为00000100,那么x^ (1<<i)=10101010^00000100=10101110,以此类推。我们发现,x ^= (1<<i)是在将x的第i位翻转而同时不影响其他位。

         因此P1 ^= (1<<0)实际是在翻转P0口第一位的值,因此也就是在闪烁LD1灯。


    上面三个例子实际都是采用了软件查询法。即main函数会每次进入到溢出事件函数里去判断TF0或1是否等于1,这样就浪费了大量CPU时间。同时,实时性差,假如在执行Timer0_Overflow()的时候timer1也溢出了,这时候timer1的溢出事件就没有及时处理。因此下面我们要引入中断系统。


    二、中断系统

    中断系统是一套硬件电路,它可以在每个机器周期对所有的外设的标志位作查询。相比于前面的软件查询(if(xx==1)),中断系统也可以叫做硬件查询。51的中断系统可查询以下6个标志位。

    IE0(TCON.1),外部中断0中断请求标志位。

    IT1(TCON.2),外部中断1触发方式控制位。

    IE1(TCON.3),外部中断1中断请求标志位。

    TF0(TCON.5),定时/计数器T0溢出中断请求标志位。

    TF1(TCON.7),定时/计数器T1溢出中断请求标志位。       

    RI(SCON.0)或TI(SCON.1),串行口中断请求标志。当串行口接收完一帧串行数据时置位RI或当串行口发送完一帧串行数据时置位TI,向CPU申请中断。 

    当中断系统查询到外设的标志位变为1时,中断系统可暂停当前的主循环,并且将程序跳转到用户预先指定的函数中执行。要启动中断系统,必须先进行中断初始化,其流程如下:

    a、是否要查询外设标志(EA=0或EA=1,EA 也叫 CPU中断允许(总允许)位)

    b、查询到标志1,是否要跳程序

    c、跳转的目标函数,即中断服务子函数

    所以在使用定时器中断时,我们只需要首先初始化中断系统,开启总中断(相当于总开关),开启定时器对应的控制位(相当于支路开关),再初始化定时器即可。中断系统作为单片机的外设,只有在某个中断产生时才会打断主循环,并由相应的中断号引入到相应的中断服务子函数。下图是6个中断标志位的信息。



    实验四、使用中断系统实现LD1灯每1秒钟闪烁。

    #include "reg51.h"
    
    void Timer0_Init()
    {
    	TMOD|=0x01;
    	TH0=56320/256;	 //计数起点为56320 ==10ms溢出一次
    	TL0=56320%256;
    	TR0=1;
    }
    
    void Timer1_Init()
    {
    	
    }
    
    void ISR_Init()	   //初始化中断系统
    {
    	EA=1; //启动中断系统
    	EX0=0; //-->IE0
    	ET0=1; //-->TF0 控制位置1,表明当TF0置1时,中断系统将介入 
    	EX1=0; //-->IE1
    	ET1=0; //-->TF1
    	ES=0; //-->RI,TI
    
    }
    
    //以下中断服务子程序,我们希望中断系统来调用,而不是我们在main函数里面调用,因此使用interrupt. */
    
    void IE0_isr() interrupt 0
    {
    
    }
    
    /*void TF0_isr()	interrupt 1	 //71.1ms 进入一次,但如果要求10MS进来一次呢?
    {
    	static char c;
    	c++;
    	if(c==14)
    	{
    		P1^=(1<<0);
    		c=0;
    	}
    }
    */
    void TF0_isr()	interrupt 1	 //10ms 进入一次
    {
    	static char c;
    	TH0=56320/256;	 //重装初值
    	TL0=56320%256;
    	c++;
    	if(c==100)
    	{
    		P1^=(1<<0);
    		c=0;
    	}
    }
    
    void IE1_isr()	interrupt 2
    {
    
    }
    
    void TF1_isr() interrupt 3
    {
    	
    }
    
    void RI_TI_isr() interrupt 4
    {
    
    }
    
    void main()
    {
    	 Timer0_Init();
    	 Timer1_Init();
    	 ISR_Init();
    
    	 while(1)
    	 {
    	 	 //...
    		 //发现溢出后,中断系统根据中断号寻找中断子服务函数,并强行暂停主循环并进入子函数
    		 //...
    	 }
    }
    显然使用中断系统查询得到的1s更为精确。因为中断系统独立于main函数运行。另外本程序还预装了timer0的初值,这样的话就可以实现比71ms更小的时间片,比如要求10ms就进入中断。关于初值的设定,请参考下图。


    实验五、用定时器实现数码管显示1234。

    //数码管的定时扫描,每5ms显示一个数码管,也就是说相同的数码管,每20ms会被重新装入同样的数值,根据人眼的延迟效应,人眼观测到的数码管上的数值是静态的。
     #include "reg51.h"
     unsigned int count;
     extern void load_smg();
     void Timer0_Init()
     {
     	TMOD|=0X01;
    	TH0=60928/256;
    	TL0=60928%256;//每5ms进入一次中断
    	TR0=1;
     }
    
    void isr_Init()
     {
     	EA=1;
    	ET0=1; //TF0 如果这个标志为1,进入中断子函数
     }
    
     void TF0_isr() interrupt 1
     {
     	TH0=60928/256;
    	TL0=60928%256;//重装初值
    	load_smg();
     }
    
     void main()
     {
     	Timer0_Init();
     	isr_Init();
    	while(1)
    	{
    	
    	}
    
     }
    
     #include "reg51.h" 
      //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     char smgbuf[4]={1,2,3,4}; //从RAM的smgbuf这个地址开始连续存放4个数,并且每个数占一个单元。
     extern unsigned int count;	//外部申明,表示并不在这里申明
    
    void fill_smgbuf() //向LED缓冲区填充数据
    {
    	smgbuf[0]=count/1000;  //千位
    	smgbuf[1]=(count%1000)/100;  //百位
    	smgbuf[2]=((count%1000)%100)/10;   //十位
    	smgbuf[3]=((count%1000)%100)%10;   //个位
    }
    
    void load_smg()   //将数码管显示缓冲区的数据,显示到数码管上
     {
     	static char i;
    	fill_smgbuf();
    	i++;
    	if(i>=4)
    	{
    		i=0;
    	}
    	P0=0xFF;   //消除上一个循环的影子
    	P2 = ~(1<<i);
    	P0 = seg[smgbuf[i]];	
     }

    实验六、实现按钮控制数码管上的数值加1或减1,并且当按住按钮不放时,能实现快速的增减。


    这里的关键点在于如何实现快速增减,具体请详细分析代码。代码链接点击打开链接

    展开全文
  • JS设置定时器和清除定时器

    万次阅读 多人点赞 2018-09-03 14:50:52
    JS设置定时器和清除定时器  在做项目中难免会碰到需要实时刷新,动画依次出现等等需求,这时候就需要定时器登上我们的代码舞台了,所以今天我们就先来了解一下JS定时器的设置和清除吧。 一、启用定时器  window...

    JS设置定时器和清除定时器

       在做项目中难免会碰到需要实时刷新,动画依次出现等等需求,这时候就需要定时器登上我们的代码舞台了,所以今天我们就先来了解一下JS定时器的设置和清除吧。

    • 一、启用定时器

       window对象提供了两个方法来实现定时器的效果,分别是window.setTimeout()和window.setInterval。其中前者可以使一段代码在指定时间后运行;而后者则可以使一段代码每过指定时间就运行一次。它们的原型如下:

       window.setTimeout(code,millisec);

       window.setInterval(code,millisec);

       其中,code可以是用引号括起来的一段代码,也可以是一个函数名,到了指定的时间,系统便会自动调用该函数,当使用函数名作为调用句柄时,不能带有任何参数;而使用字符串时,则可以在其中写入要传递的参数。两个方法中的第二个参数是millisec,表示延时或者重复执行的毫秒数。

    具体写法如下

    • 函数名,不带参数

    setTimeout (test,1000);           //1秒后执行

    字符串,可以执行的代码

    setTimeout ('test()',1000);       //1秒后执行

    • 匿名函数

    setTimeout (function(){},1000);   //1秒后执行

    注:setInterval的用法与setTimeout一样

    • 调用函数,带参数

    setTimeout ('test(参数)',1000);      //1秒后执行

    注:如果不小心写成了setTimeout (test(参数),1000);test方法就会立即执行哦。

    DEMO代码:

     

    执行结果:

     

    二、清除定时器

       由于定时器在调用时,都会返回一个整形的数字,该数字代表定时器的序号,即第多少个定时器,所以定时器的清除要借助于这个返回的数字。

       定时器清除的方法:clearTimeout(obj)和clearInterval(obj)。

       要清除定时器,就必须在用定时器的时候,定义一个变量来记录定时器的返回值。如下:

    //setTimeout 1000ms后执行1次
    var test1 = setTimeout(function(){
        //your codes
    },1000);
     
    //setInterval 每隔1000ms执行一次
    var test2 = setInterval(function(){
       //your codes
    },1000)
     
    //清除Timeout的定时器,传入变量名(创建Timeout定时器时定义的变量名)
    clearTimeout(test1);
     
    //清除Interval的定时器,传入变量名(创建Interval定时器时定义的变量名)
    clearInterval(test2);
    

     

       注:有时候在写的时候,还会习惯将清空的定时器的变量置空,这样写既可以释放内存,也可以便于后边代码的判断。

    原文链接:https://www.h5anli.com/articles/201705/setimeone.html

    展开全文
  • 定时器的几种实现方式

    千次阅读 2019-02-27 23:19:28
    1 前言 在开始正题之前,先闲聊几句。...这个规律具有普适应,看看“定时器”这个例子,往应用层研究,有 Quartz,Spring Schedule 等框架;往分布式研究,又有 SchedulerX,ElasticJob 等分布式任...

    1 前言

    在开始正题之前,先闲聊几句。有人说,计算机科学这个学科,软件方向研究到头就是数学,硬件方向研究到头就是物理,最轻松的是中间这批使用者,可以不太懂物理,不太懂数学,依旧可以使用计算机作为自己谋生的工具。这个规律具有普适应,看看“定时器”这个例子,往应用层研究,有 Quartz,Spring Schedule 等框架;往分布式研究,又有 SchedulerX,ElasticJob 等分布式任务调度;往底层实现看,又有多种定时器实现方案的原理、工作效率、数据结构可以深究…简单上手使用一个框架,并不能体现出个人的水平,如何与他人构成区分度?我觉得至少要在某一个方向有所建树:

    1. 深入研究某个现有框架的实现原理,例如:读源码
    2. 将一个传统技术在分布式领域很好地延伸,很多成熟的传统技术可能在单机 work well,但分布式场景需要很多额外的考虑。
    3. 站在设计者的角度,如果从零开始设计一个轮子,怎么利用合适的算法、数据结构,去实现它。

    回到这篇文章的主题,我首先会围绕第三个话题讨论:设计实现一个定时器,可以使用什么算法,采用什么数据结构。接着再聊聊第一个话题:探讨一些优秀的定时器实现方案。

    2 理解定时器

    很多场景会用到定时器,例如

    1. 使用 TCP 长连接时,客户端需要定时向服务端发送心跳请求。
    2. 财务系统每个月的月末定时生成对账单。
    3. 双 11 的 0 点,定时开启秒杀开关。

    定时器像水和空气一般,普遍存在于各个场景中,一般定时任务的形式表现为:经过固定时间后触发、按照固定频率周期性触发、在某个时刻触发。定时器是什么?可以理解为这样一个数据结构:

    存储一系列的任务集合,并且 Deadline 越接近的任务,拥有越高的执行优先级 在用户视角支持以下几种操作: NewTask:将新任务加入任务集合 Cancel:取消某个任务 在任务调度的视角还要支持: Run:执行一个到期的定时任务

    判断一个任务是否到期,基本会采用轮询的方式,每隔一个时间片 去检查 最近的任务 是否到期,并且,在 NewTask 和 Cancel 的行为发生之后,任务调度策略也会出现调整。

    说到底,定时器还是靠线程轮询实现的。

    3 数据结构

    我们主要衡量 NewTask(新增任务),Cancel(取消任务),Run(执行到期的定时任务)这三个指标,分析他们使用不同数据结构的时间/空间复杂度。

    3.1 双向有序链表

    在 Java 中,LinkedList 是一个天然的双向链表

    NewTask:O(N) Cancel:O(1) Run:O(1) N:任务数

    NewTask O(N) 很容易理解,按照 expireTime 查找合适的位置即可;Cancel O(1) ,任务在 Cancel 时,会持有自己节点的引用,所以不需要查找其在链表中所在的位置,即可实现当前节点的删除,这也是为什么我们使用双向链表而不是普通链表的原因是 ;Run O(1),由于整个双向链表是基于 expireTime 有序的,所以调度器只需要轮询第一个任务即可。

    3.2 堆

    在 Java 中,PriorityQueue 是一个天然的堆,可以利用传入的 Comparator 来决定其中元素的优先级。

    NewTask:O(logN) Cancel:O(logN) Run:O(1) N:任务数

    expireTime 是 Comparator 的对比参数。NewTask O(logN) 和 Cancel O(logN) 分别对应堆插入和删除元素的时间复杂度 ;Run O(1),由 expireTime 形成的小根堆,我们总能在堆顶找到最快的即将过期的任务。

    堆与双向有序链表相比,NewTask 和 Cancel 形成了 trade off,但考虑到现实中,定时任务取消的场景并不是很多,所以堆实现的定时器要比双向有序链表优秀。

    3.3 时间轮

    Netty 针对 I/O 超时调度的场景进行了优化,实现了 HashedWheelTimer 时间轮算法。

    HashedWheelTimer 是一个环形结构,可以用时钟来类比,钟面上有很多 bucket ,每一个 bucket 上可以存放多个任务,使用一个 List 保存该时刻到期的所有任务,同时一个指针随着时间流逝一格一格转动,并执行对应 bucket 上所有到期的任务。任务通过取模决定应该放入哪个 bucket 。和 HashMap 的原理类似,newTask 对应 put,使用 List 来解决 Hash 冲突。

    以上图为例,假设一个 bucket 是 1 秒,则指针转动一轮表示的时间段为 8s,假设当前指针指向 0,此时需要调度一个 3s 后执行的任务,显然应该加入到 (0+3=3) 的方格中,指针再走 3 次就可以执行了;如果任务要在 10s 后执行,应该等指针走完一轮零 2 格再执行,因此应放入 2,同时将 round(1)保存到任务中。检查到期任务时只执行 round 为 0 的, bucket 上其他任务的 round 减 1。

    再看图中的 bucket5,我们可以知道在 1*8+5=13s 后,有两个任务需要执行,在 2*8+5=21s 后有一个任务需要执行。

    NewTask:O(1) Cancel:O(1) Run:O(M) Tick:O(1) M: bucket ,M ~ N/C ,其中 C 为单轮 bucket 数,Netty 中默认为 512

    时间轮算法的复杂度可能表达有误,比较难算,仅供参考。另外,其复杂度还受到多个任务分配到同一个 bucket 的影响。并且多了一个转动指针的开销。

    传统定时器是面向任务的,时间轮定时器是面向 bucket 的。

    构造 Netty 的 HashedWheelTimer 时有两个重要的参数:tickDurationticksPerWheel

    1. tickDuration:即一个 bucket 代表的时间,默认为 100ms,Netty 认为大多数场景下不需要修改这个参数;
    2. ticksPerWheel:一轮含有多少个 bucket ,默认为 512 个,如果任务较多可以增大这个参数,降低任务分配到同一个 bucket 的概率。

    3.4 层级时间轮

    Kafka 针对时间轮算法进行了优化,实现了层级时间轮 TimingWheel

    如果任务的时间跨度很大,数量也多,传统的 HashedWheelTimer 会造成任务的 round 很大,单个 bucket 的任务 List 很长,并会维持很长一段时间。这时可将轮盘按时间粒度分级:

    现在,每个任务除了要维护在当前轮盘的 round,还要计算在所有下级轮盘的round。当本层的round为0时,任务按下级 round 值被下放到下级轮子,最终在最底层的轮盘得到执行。

    NewTask:O(H) Cancel:O(H) Run:O(M) Tick:O(1) H:层级数量

    设想一下一个定时了 3 天,10 小时,50 分,30 秒的定时任务,在 tickDuration = 1s 的单层时间轮中,需要经过:3*24*60*60+10*60*60+50*60+30 次指针的拨动才能被执行。但在 wheel1 tickDuration = 1 天,wheel2 tickDuration = 1 小时,wheel3 tickDuration = 1 分,wheel4 tickDuration = 1 秒 的四层时间轮中,只需要经过 3+10+50+30 次指针的拨动!

    相比单层时间轮,层级时间轮在时间跨度较大时存在明显的优势。

    4 常见实现

    4.1 Timer

    JDK 中的 Timer 是非常早期的实现,在现在看来,它并不是一个好的设计。

    // 运行一个一秒后执行的定时任务
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            // do sth
        }
    }, 1000);
    复制代码

    使用 Timer 实现任务调度的核心是 TimerTimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

    public class Timer {
        private final TaskQueue queue = new TaskQueue();
        private final TimerThread thread = new TimerThread(queue);
    }
    复制代码

    其中 TaskQueue 是使用数组实现的一个简易的堆。另外一个值得注意的属性是 TimerThreadTimer 使用唯一的线程负责轮询并执行任务。Timer 的优点在于简单易用,但也因为所有任务都是由同一个线程来调度,因此整个过程是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

    轮询时如果发现 currentTime < heapFirst.executionTime,可以 wait(executionTime - currentTime) 来减少不必要的轮询时间。这是普遍被使用的一个优化。

    1. Timer 只能被单线程调度
    2. TimerTask 中出现的异常会影响到 Timer 的执行。

    由于这两个缺陷,JDK 1.5 支持了新的定时器方案 ScheduledExecutorService

    4.2 ScheduledExecutorService

    // 运行一个一秒后执行的定时任务
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    service.scheduleA(new Runnable() {
        @Override
        public void run() {
            //do sth
        }
    }, 1, TimeUnit.SECONDS);
    复制代码

    相比 TimerScheduledExecutorService 解决了同一个定时器调度多个任务的阻塞问题,并且任务异常不会中断 ScheduledExecutorService

    ScheduledExecutorService 提供了两种常用的周期调度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。

    ScheduleAtFixedRate 每次执行时间为上一次任务开始起向后推一个时间间隔,即每次执行时间为 : initialDelay, initialDelay+period, initialDelay+2*period, …

    ScheduleWithFixedDelay 每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay, ...

    由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔的任务调度。

    ScheduledExecutorService 底层使用的数据结构为 PriorityQueue,任务调度方式较为常规,不做特别介绍。

    4.3 HashedWheelTimer

    Timer timer = new HashedWheelTimer();
    //等价于 Timer timer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 512);
    timer.newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            //do sth
        }
    }, 1, TimeUnit.SECONDS);
    复制代码

    前面已经介绍过了 Netty 中 HashedWheelTimer 内部的数据结构,默认构造器会配置轮询周期为 100ms,bucket 数量为 512。其使用方法和 JDK 的 Timer 十分相似。

    private final Worker worker = new Worker();// Runnable
    private final Thread workerThread;// Thread
    复制代码

    由于篇幅限制,我并不打算做详细的源码分析,但上述两行来自 HashedWheelTimer 的代码阐释了一个事实:HashedWheelTimer 内部也同样是使用单个线程进行任务调度。与 JDK 的 Timer 一样,存在”前一个任务执行时间过长,影响后续定时任务执行“的问题。

    理解 HashedWheelTimer 中的 ticksPerWheel,tickDuration,对二者进行合理的配置,可以使得用户在合适的场景得到最佳的性能。

    5 最佳实践

    5.1 选择合适的定时器

    毋庸置疑,JDK 的 Timer 使用的场景是最窄的,完全可以被后两者取代。如何在 ScheduledExecutorServiceHashedWheelTimer 之间如何做选择,需要区分场景,做一个简单的对比:

    1. ScheduledExecutorService 是面向任务的,当任务数非常大时,使用堆(PriorityQueue)维护任务的新增、删除会导致性能下降,而 HashedWheelTimer 面向 bucket,设置合理的 ticksPerWheel,tickDuration ,可以不受任务量的限制。所以在任务非常多时,HashedWheelTimer 可以表现出它的优势。
    2. 相反,如果任务量少,HashedWheelTimer 内部的 Worker 线程依旧会不停的拨动指针,虽然不是特别消耗性能,但至少不能说:HashedWheelTimer 一定比 ScheduledExecutorService 优秀。
    3. HashedWheelTimer 由于开辟了一个 bucket 数组,占用的内存会稍大。

    上述的对比,让我们得到了一个最佳实践:在任务非常多时,使用 HashedWheelTimer 可以获得性能的提升。例如服务治理框架中的心跳定时任务,服务实例非常多时,每一个客户端都需要定时发送心跳,每一个服务端都需要定时检测连接状态,这是一个非常适合使用 HashedWheelTimer 的场景。

    5.2 单线程与业务线程池

    我们需要注意HashedWheelTimer 使用单线程来调度任务,如果任务比较耗时,应当设置一个业务线程池,将HashedWheelTimer 当做一个定时触发器,任务的实际执行,交给业务线程池。

    如果所有的任务都满足: taskNStartTime - taskN-1StartTime > taskN-1CostTime,即任意两个任务的间隔时间小于先执行任务的执行时间,则无需担心这个问题。

    5.3 全局定时器

    实际使用 HashedWheelTimer 时,应当将其当做一个全局的任务调度器,例如设计成 static 。时刻谨记一点:HashedWheelTimer 对应一个线程,如果每次实例化 HashedWheelTimer,首先是线程会很多,其次是时间轮算法将会完全失去意义。

    5.4 为 HashedWheelTimer 设置合理的参数

    ticksPerWheel,tickDuration 这两个参数尤为重要,ticksPerWheel 控制了时间轮中 bucket 的数量,决定了冲突发生的概率,tickDuration 决定了指针拨动的频率,一方面会影响定时的精度,一方面决定 CPU 的消耗量。当任务数量非常大时,考虑增大 ticksPerWheel;当时间精度要求不高时,可以适当加大 tickDuration,不过大多数情况下,不需要 care 这个参数。

    5.5 什么时候使用层级时间轮

    当时间跨度很大时,提升单层时间轮的 tickDuration 可以减少空转次数,但会导致时间精度变低,层级时间轮既可以避免精度降低,又避免了指针空转的次数。如果有时间跨度较长的定时任务,则可以交给层级时间轮去调度。此外,也可以按照定时精度实例化多个不同作用的单层时间轮,dayHashedWheelTimer、hourHashedWheelTimer、minHashedWheelTimer,配置不同的 tickDuration,此法虽 low,但不失为一个解决方案。Netty 设计的 HashedWheelTimer 是专门用来优化 I/O 调度的,场景较为局限,所以并没有实现层级时间轮;而在 Kafka 中定时器的适用范围则较广,所以其实现了层级时间轮,以应对更为复杂的场景。

    6 参考资料

    [1] www.ibm.com/developerwo…

    [2] novoland.github.io/并发/2014/07/…

    [3] www.cs.columbia.edu/~nahum/w699…

    欢迎关注我的微信公众号:「Kirito的技术分享」,关于文章的任何疑问都会得到回复,带来更多 Java 相关的技术分享。

    展开全文
  • 《STM32中文参考手册V10》-第14章通用定时器   STM32的定时器 STM32F103ZET6一共有8个定时器,其中分别为: 高级定时器(TIM1、TIM8);通用定时器(TIM2、TIM3、TIM4、TIM5);基本定时器(TIM6、TIM7)。 ...
  • JS定时调用和延时调用

    千次阅读 2018-12-20 09:46:23
    定时调用 setInterval() 定时调用,将一个函数每隔一段时间执行一次 需要两个参数:1)回调函数 ...他可以接受任何类型的参数,若参数是有效的定时器标识,则停止定时器;若无效,则不作任何行为。 切...
  • 延时调用与定时调用

    2019-10-29 10:44:19
    注意区分执行时间和间隔时间 执行时间:定/延时器代码执行的时间 间隔时间:定/延时器设置每隔 xxxms 执行 一、延时器(setTimeout) 1.延时器(setTimeout)的工作方式是:当设定一个延时器是5s后进行时,并不代表...
  • 定时调用和延时调用

    2019-11-12 03:05:08
    定时器,三分钟停止 <body> 分:<input type="text" value="0"> 秒:<input type="text" value="0"> <script type="text/javascript"> var sec=document.getElementsByTagName('input')...
  • 定时器、延时调用

    2020-06-09 22:57:54
    定时调用 window.onload = function(){ //获取count var count = document.getElementById("count"); //使count中的内容,自动切换 /* * JS的程序的执行速度是非常非常快的 ...
  • setInterval(回调函数,每次调用间隔时间) ...clearInterval关闭定时器 <script type="text/javascript"> window.onload = function(){ var count = document.getElementById("count"); ...
  • HTML 之 定时器

    万次阅读 2018-04-18 20:49:31
    定时器 &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;title&gt;定时器&lt;/title&gt...
  • 定时器事件 1.timerEvent widget.cpp文件 #include "widget.h" #include "ui_widget.h" #include<QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this...
  • Qt之QTimer(定时器)

    千次阅读 2019-08-13 09:39:47
    想要掌握QTimer只需掌握setInterval(设置定时时间,单位ms)、start(启动定时器),和定时时间到时所发出的信号timeout()即可。程序设计的思路: 1.先设定定时周期 2.绑定timeout()信号到自定义槽函数 3.调用start...
  • JMeter(八):定时器Timer经典介绍

    万次阅读 2020-10-23 17:21:32
    背景:JMeter常被定义成性能测试工具或是自动化测试工具,都没错,同时还可以作为接口测试及web功能测试,关键使用者根据业务需求选择使用其功能;性能测试方向:后起之秀JMeter与革命前辈Loadrunner的比较,JMeter...
  • 【STM32】通用定时器的PWM输出(实例:PWM输出)

    万次阅读 多人点赞 2019-02-13 17:43:57
    STM32F1xx官方资料: ...STM32的通用定时器分为TIM2、TIM3、TIM4、TIM5,而每个定时器都有独立的4个通道可以用来作为:输入捕获、输出比较、PWM输出、单脉冲模式输出等。 STM32的定时器除了TIM6和TIM7(基本定时器...
  • JS 轮播图 图片切换(定时器

    千次阅读 多人点赞 2019-09-22 18:47:43
    JS 轮播图 图片切换(定时器) 这次的轮播图与上次的图片切换相比,仅仅是加上了定时器,使其可以自动切换。 上次的图片切换的链接:https://blog.csdn.net/qq_38318589/article/details/99050117
  • STM32通用定时器使用详解

    万次阅读 多人点赞 2019-06-29 19:12:46
    1.通用定时器基本介绍 通用定时器包括TIM2、TIM3、TIM4和TIM5 STM32通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。 每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。...
  • //定时器 异步运行 function hello(){ alert("hello"); } //使用方法名字执行方法 var t1 = window.setTimeout(hello,1000); var t2 = window.setTimeout("hello()",3000);//使用字符串执行方法 window.clearTim
  • 【STM32】HAL库 STM32CubeMX教程六----定时器中断

    万次阅读 多人点赞 2020-03-26 10:10:44
    今天我们来学习定时器,32的定时器有着非常丰富的功能,输入捕获/输出比较,PWM,中断等等。是我们学习STM32最频繁使用到的外设之一,所以一定要掌握好,这节我们讲解定时器中断,本系列教程将对应外设原理,HAL库与...
  • STM32F1xx官方资料: 《STM32中文参考手册V10》-第14章 通用定时器 ...这里主要讲解一下左下部分(输入捕获),其他两个部分可以参考文章:【STM32】通用定时器的基本原理(实例:定时器中断)。 输入捕获...
1 2 3 4 5 ... 20
收藏数 283,514
精华内容 113,405
关键字:

定时器