精华内容
下载资源
问答
  • 定时器有几种实现方式

    千次阅读 2019-11-06 21:31:35
    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,我们可以知道在 $18+5=13s$ 后,有两个任务需要执行,在 $28+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 时有两个重要的参数: tickDuration 和 ticksPerWheel。

     

    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 的单层时间轮中,需要经过:$3246060+106060+5060+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 实现任务调度的核心是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

     

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

     

    其中 TaskQueue 是使用数组实现的一个简易的堆,前面我们已经介绍过了堆这个数据结构的特点。另外一个值得注意的属性便是 TimerThread,一个 Timer 使用了唯一的线程负责了轮询和任务的执行。 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);

     

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

     

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

     

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

     

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

     

    由此可见,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 的使用方式也十分相同。

     

    private final Worker worker = new Worker();// Runnable
    private final Thread workerThread;// Thread

     

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

     

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

     

    5 最佳实践

     

    5.1 选择合适的定时器

     

    毋庸置疑,JDK 的 Timer 使用的场景是最窄的,完全可以被后两者取代。如何在 ScheduledExecutorService 和 HashedWheelTimer 之间如何做选择,还是要区分场景来看待。

     

    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,但不失为一个解决方案。

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

    千次阅读 2017-12-08 18:09:24
    java定时器几种实现方式 import java.util.Calendar; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimeTest { public static void main(String[] args) {...

    java定时器的几种实现方式

     

     

    import java.util.Calendar;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
     
    public class TimeTest {
        public static void main(String[] args) {
            timer1();
            //timer2();
            //timer3();
            //timer4();
        }
     
        // 第一种方法:设定指定任务task在指定时间time执行 schedule(TimerTask task, Date time)
        public static void timer1() {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                public void run() {
                    System.out.println("-------设定要指定任务--------");
                }
            }, 2000);// 设定指定的时间time,此处为2000毫秒
        }
     
        // 第二种方法:设定指定任务task在指定延迟delay后进行固定延迟peroid的执行
        // schedule(TimerTask task, long delay, long period)
        public static void timer2() {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                public void run() {
                    System.out.println("-------设定要指定任务--------");
                }
            }, 1000, 5000);
        }
     
        // 第三种方法:设定指定任务task在指定延迟delay后进行固定频率peroid的执行。
        // scheduleAtFixedRate(TimerTask task, long delay, long period)
        public static void timer3() {
            Timer timer = new Timer();
            timer.scheduleAtFixedRate(new TimerTask() {
                public void run() {
                    System.out.println("-------设定要指定任务--------");
                }
            }, 1000, 2000);
        }
        // 第四种方法:安排指定的任务task在指定的时间firstTime开始进行重复的固定速率period执行.
        // Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
        public static void timer4() {
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.HOUR_OF_DAY, 12); // 控制时
            calendar.set(Calendar.MINUTE, 0);       // 控制分
            calendar.set(Calendar.SECOND, 0);       // 控制秒
     
            Date time = calendar.getTime();         // 得出执行任务的时间,此处为今天的12:00:00
     
            Timer timer = new Timer();
            timer.scheduleAtFixedRate(new TimerTask() {
                public void run() {
                    System.out.println("-------设定要指定任务--------");
                }
            }, time, 1000 * 60 * 60 * 24);// 这里设定将延时每天固定执行
        }
    }

     

     

     

     

     


     

    展开全文
  • javaweb的几种定时方式助于了解定时的工作原理。对于定时器不了解的朋友可以看看
  • 定时器几种实现方式

    千次阅读 2017-08-03 13:32:49
    iOS中常常会用到定时器,我们常用到的种方式:NSTimer、CADisplayLink和GCD 一、NSTimer (1)创建 NStimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector...

    iOS中常常会用到定时器,我们常用到的有三种方式:NSTimer、CADisplayLink和GCD

    一、NSTimer

    (1)创建

    NStimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];

    //TimerInterval执行之前等待的时间,1.0表示执行的时间间隔 repeats标示是否重复

    (2)手动开始

    - (void)fire; 

    (3)停止:调用创建方法后,target对象的计数器会加1,直到执行完毕,自动减1。如果是循环执行的话,就必须手动关闭,否则可以不执行释放方法。

    - (void)invalidate;

    (4)存在延迟

    不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。

    运行在主线程上  当使用NSTimer的scheduledTimerWithTimeInterval方法时。事实上此时Timer会被加入到当前线程的Run Loop中,且模式是默认的NSDefaultRunLoopMode。而如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行。

         为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到Run Loop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合,官方参考文档 

      [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

     

    二、CADisplayLink

    (1)使用

    复制代码
    //创建
    + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
    
    - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
    
    - (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
    
    //停止
    - (void)invalidate;
     //设置定时器
        CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(action)];
       //开启定时器
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    -(void)stopLink {
        // 关闭定时器
        [self.link invalidate];
        self.link = nil;
    }
     (2)特性
              CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒
    
             iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。
            如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。
    (3)使用场景
    
          从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
    
    
    
    三、GCD方式
    
    执行一次
    
    double delayInSeconds = 2.0;
    
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ //执行事件});
    
    重复执行
    
    NSTimeInterval period = 1.0; //设置时间间隔
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
     dispatch_source_set_event_handler(timer, ^{
    
            NSLog(@"wakeup");
    
            dispatch_source_cancel(timer);
    
            NSLog(@"end:%@",[formatter stringFromDate:[NSDate date]]);
    
        });
    
        dispatch_source_set_cancel_handler(timer, ^{
    
            NSLog(@"cancel");
    
            NSLog(@"end:%@",[formatter stringFromDate:[NSDate date]]);
    
        });
    
    //开启定时器
    
    dispatch_resume(_timer);
    复制代码

    五、CADisplayLink 与 NSTimer 有什么不同

              iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
    NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。
          CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。


    转自:http://www.cnblogs.com/LyChen/p/5109423.html


    展开全文
  • Qt 定时器几种方式 摘要: Qt中定时器的使用两种方法,一种是使用QObject类提供的定时器startTimer,还有一种就是使用QTimer类。 方法介绍: 共有方法:   QTimer(QObject *parent = Q_NULLPTR) ...

    Qt 定时器的几种方式

    摘要:

    • Qt中定时器的使用有两种方法,一种是使用QObject类提供的定时器startTimer,还有一种就是使用QTimer类。

    方法介绍:

    • 共有方法:
     

    QTimer(QObject *parent = Q_NULLPTR)

    构造函数

     

    ~QTimer()

    析构函数

    int

    interval() const

    此属性保存以毫秒为单位的超时间隔
    此属性的默认值为0。超时间隔为0的QTimer将在窗口系统的事件队列中的所有事件处理完毕后立即超时。

    std::chrono::milliseconds

    intervalAsDuration() const

    将此计时器的间隔返回为std::chrono::milliseconds对象。
    该函数在Qt 5.8中引入。

    bool

    isActive() const

    如果计时器正在运行(挂起),返回true;否则返回false。

    bool

    isSingleShot() const

    此属性保存计时器是否为单发计时器
    单发计时器只触发一次,非单发计时器每隔毫秒触发一次。
    此属性的默认值为false。

    int

    remainingTime() const

    此属性保存剩余时间(以毫秒为单位)
    返回计时器的剩余值(以毫秒为单位),直到超时。如果计时器不活动,返回的值将为-1。如果计时器过期,返回的值将为0

    std::chrono::milliseconds

    remainingTimeAsDuration() const

    以std::chrono::milliseconds对象的形式返回计时器对象中剩余的时间。如果该计时器到期或过期,返回的值是std::chrono::milliseconds::zero()。如果找不到剩余的时间或计时器不活动,则此函数返回负持续时间。

    void

    setInterval(int msec)

    此属性保存以毫秒为单位的超时间隔
    此属性的默认值为0。超时间隔为0的QTimer将在窗口系统的事件队列中的所有事件处理完毕后立即超时。

    void

    setInterval(std::chrono::milliseconds value)

    设置超时时间间隔

    void

    setSingleShot(bool singleShot)

    此属性保存计时器是否为单发计时器
    单发计时器只触发一次,非单发计时器每隔毫秒触发一次。
    此属性的默认值为false。

    void

    setTimerType(Qt::TimerType atype)

    控制计时器的准确性
    此属性的默认值是Qt:: crude setimer。

    void

    start(std::chrono::milliseconds msec)

    这是一个重载函数。
    启动或重新启动计时器,超时持续时间为msec毫秒。
    如果计时器已经在运行,它将停止并重新启动。
    如果singleShot为真,计时器只会被激活一次。
    该函数在Qt 5.8中引入。

    int

    timerId() const

    如果计时器正在运行,则返回计时器的ID;否则返回1。

    Qt::TimerType

    timerType() const

    控制计时器的准确性
    此属性的默认值是Qt:: crude setimer。

    enum Qt::TimerType

    • 计时器类型指示计时器的精确程度,如果使用定时器要求精度相对较高的时候要通过setTimerType(Qt::TimerType atype)设置这个参数或者通过初始化传递此参数。

    Constant

    Value

    Description

    Qt::PreciseTimer

    0

    Precise timers try to keep millisecond accuracy

    精确定时器试图保持毫秒精度

    Qt::CoarseTimer

    1

    Coarse timers try to keep accuracy within 5% of the desired interval

    粗定时器试图使精确度保持在所需间隔的5%以内

    Qt::VeryCoarseTimer

    2

    Very coarse timers only keep full second accuracy

    非常粗糙的定时器只能保持完全的秒精度

    Qt定时器的使用方法

    1.简单的定时器:

     

    • 使用这个函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象
    • 这个静态函数在给定的时间间隔后调用一个槽。

      使用下面的方法:

    • Static Public Members:
      void singleShot(int msec, const QObject *receiver, const char *member)
      void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
      void singleShot(int msec, const QObject *receiver, PointerToMemberFunction method)
      void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method)
      void singleShot(int msec, Functor functor)
      void singleShot(int msec, Qt::TimerType timerType, Functor functor)
      void singleShot(int msec, const QObject *context, Functor functor)
      void singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor)
      void singleShot(std::chrono::milliseconds msec, const QObject *receiver, const char *member)
      void singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, const QObject *receiver, const char *member)

      例子:

    • 这是一次性定时器到了定时间隔之后只发送一次信号槽函数只处理一次。
    • QTimer::singleShot(600000, &app, SLOT(quit()));

       

    2.QObject中的startTimer

    • int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
    • 启动计时器并返回计时器标识符,如果无法启动计时器,则返回零。
    • 计时器事件将在每个间隔毫秒内发生,直到调用killTimer()。如果interval为0,那么每当不再需要处理窗口系统事件时,计时器事件就会发生一次。
    • 当计时器事件发生时,使用QTimerEvent事件参数类调用虚拟timerEvent()函数。重新实现此函数以获取计时器事件。
    • 如果有多个计时器在运行,可以使用QTimerEvent::timerId()来查找激活的计时器。

     

    • int QObject::startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer)
    • 这是一个重载函数。用法与上面的一样:
    • 如果时间等于std::chrono::duration::zero()::{ 关于介绍请看:https://zh.cppreference.com/w/cpp/chrono/duration }

      实例代码创建步骤使用说明:

    class Widget2 : public QWidget
    {
        Q_OBJECT
    public:
        Widget2(QWidget* parent = 0 ):QWidget(parent),
            timeID(0)
        {
            QPalette palette (this->palette());
            palette.setBrush(QPalette::Background, QBrush(QColor(100,12,130)));
            this-> setPalette( palette );
            //2. 创建定时器
            timeID = startTimer(100,Qt::PreciseTimer);
    
        }
        ~Widget2(){}
    
    protected:
        // 3. 重写定时器事件,接收定时的到来
        void timerEvent(QTimerEvent *event)
        {
            // 4. 判断是否为这个定时器ID(系统可能有多个定时器Id、这种方式启动的话、全放在这里处理)
            if(event->timerId() == timeID)
            {
                //TODO function
                if(timeID)
                    killTimer(timeID);// 5.杀死定时器
                timeID = 0;
            }
        }
    
    private:
        int timeID;//1. 声明定时器ID
    };

    3.QTimer

    • QTimer类提供了重复的、单发的计时器。
    • QTimer类为计时器提供了一个高级编程接口。要使用它,创建一个QTimer,将其timeout()信号连接到适当的插槽,并调用start()。从那时起,它将以恒定的间隔发出timeout()信号。
    • 计时器的准确性取决于底层操作系统和硬件。大多数平台都支持1毫秒的分辨率,但在许多实际情况下,计时器的精度无法达到这个分辨率。
    • 准确度也取决于定时器的类型。对于Qt::PreciseTimer, QTimer将尝试保持精度在1毫秒。精确的定时器也永远不会比预期提前超时。
    • 对于Qt:: crude setimer和Qt::非常粗的timer类型,QTimer可能比预期醒得早,在这些类型的范围内:Qt:: roughsetimer和500ms的间隔的5%内
       

      实例代码创建步骤使用说明:

      

    class Widget3 : public QWidget
    {
        Q_OBJECT
    public:
        Widget3(QWidget* parent = 0 ):QWidget(parent),
            m_pTimer(nullptr)
        {
            m_pTimer = new QTimer;// 2. 创建定时器对象
            m_pTimer->setTimerType(Qt::PreciseTimer); // 3.设置定时器对象精确度模式
            connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOut_Slots()));// 4. 连接定时器超时后处理的槽函数
            m_pTimer->start(100);// 5. 开启定时器
        }
        ~Widget3(){}
    public slots:
        //6. 定时器槽函数
        void timeOut_Slots()
        {
            //TODO function
            if(m_pTimer)
                killTimer(m_pTimer->timerId());//7. 杀死定时器
            delete m_pTimer;
            m_pTimer = NULL;
        }
    
    private:
        QTimer* m_pTimer;//1. 声明定时器对象指针
    };

    Windows精确定时器:

    • 这是获取高分辨率CPU时序的常用方法。本文提出了一种更准确,可靠的解决方案,通过使用Windows API QueryPerformanceCounter和QueryPerformanceFrequency来获取高分辨率的CPU时序。
    • QueryPerformanceFrequency() - 基本介绍
    • 类型:Win32API
    • 原型:BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
    • 作用:返回硬件支持的高精度计数器的频率。
    • 返回值:非零,硬件支持高精度计数器;零,硬件不支持,读取失败。
    • QueryPerformanceFrequency() - 技术特点
    • 供WIN9X使用的高精度定时器:QueryPerformanceFrequency()和QueryPerformanceCounter(),要求计算机从硬件上支持高精度定时器。需包含windows.h头文件。
    • 支持:屏蔽底层硬件差异(有无支持HRT的硬件high resolution timer),为用户提供计时功能。 如果底层硬件支持HRT,两个API就会返回高精度
    • 的时间计数(微妙或纳秒级), 如果底层硬件不支持, 两个API的调用结果就类似于 GetTickCount () API返回给用户的时间精度(毫秒级)。

    介绍

    微软官方背景介绍:

        随着电源管理技术在当今计算机中变得越来越普遍,RDTSC指令可能无法按预期工作,这是获取高分辨率CPU时序的常用方法。本文提出了一种更准确,可靠的解决方案,通过使用Windows API QueryPerformanceCounter和QueryPerformanceFrequency来获取高分辨率的CPU时序。背景建议应用兼容性背景
    自从推出x86 P5指令集以来,许多游戏开发人员都利用读取时间戳计数器RDTSC指令来执行高分辨率计时。Windows多媒体定时器对于声音和视频处理来说足够精确,但是帧时间为十几毫秒或更短,它们没有足够的分辨率来提供增量时间信息。许多游戏在启动时仍然使用多媒体计时器来确定CPU的频率,并且他们使用该频率值来缩放RDTSC的结果以获得准确的时间。由于RDTSC的限制,Windows API通过QueryPerformanceCounter和QueryPerformanceFrequency的例程公开了访问此功能的更正确方法。
    RDTSC用于定时的这种使用受到以下基本问题的影响:
    不连续的价值观。直接使用RDTSC假定线程始终在同一处理器上运行。多处理器和双核系统不保证核心之间的循环计数器同步。当与在不同时间空闲和恢复各种核心的现代电源管理技术相结合时,这会加剧,这导致核心通常不同步。对于应用程序,这通常会导致毛刺或潜在的崩溃,因为线程在处理器之间跳转并获得导致大增量,负增量或暂停时序的定时值。
    专用硬件的可用性。RDTSC将应用程序请求的定时信息锁定到处理器的循环计数器。多年来,这是获得高精度定时信息的最佳方式,但较新的主板现在包括提供高分辨率定时信息的专用定时设备,没有RDTSC的缺点。
    CPU频率的可变性。通常假设CPU的频率在程序的生命周期内是固定的。但是,使用现代电源管理技术,这是一个不正确的假设。虽然最初仅限于笔记本电脑和其他移动设备,但许多高端台式电脑正在使用改变CPU频率的技术; 禁用其功能以保持一致的频率通常是用户不能接受的。
    建议游戏需要准确的计时信息,但您还需要以避免与使用RDTSC相关的问题的方式实现计时代码。实现高分辨率计时时,请执行以下步骤:
    使用QueryPerformanceCounter和QueryPerformanceFrequency而不是RDTSC。这些API可以使用RDTSC,但可以使用主板上的定时设备或提供高质量高分辨率定时信息的一些其他系统服务。虽然RDTSC比QueryPerformanceCounter快得多,但由于后者是一个API调用,因此它是一个可以每帧调用数百次的API而没有任何明显的影响。(尽管如此,开发人员应该尝试让他们的游戏尽可能少地调用QueryPerformanceCounter以避免任何性能损失。)
    在计算增量时,应该钳制这些值以确保定时值中的任何错误不会导致崩溃或不稳定的与时间相关的计算。钳位范围应从0(以防止负增量值)到基于最低预期帧速率的某个合理值。在您的应用程序的任何调试中,钳位可能都很有用,但是如果进行性能分析或以某种未经优化的模式运行游戏,请务必牢记这一点。
    计算单个线程上的所有时序。计算多个线程上的时序 - 例如,每个线程与特定处理器相关联 - 极大地降低了多核系统的性能
    使用Windows API SetThreadAffinityMask将该单个线程设置为保留在单个处理器上。通常,这是主要的游戏主题。虽然QueryPerformanceCounter和QueryPerformanceFrequency通常针对多个处理器进行调整,但是当线程从一个处理器移动到另一个处理器时,BIOS或驱动程序中的错误可能导致这些例程返回不同的值。因此,最好将线程保留在单个处理器上。
    所有其他线程应该运行而不收集自己的计时器数据。我们不建议使用工作线程来计算时序,因为这将成为同步瓶颈。相反,工作线程应该从主线程读取时间戳,并且因为工作线程只读取时间戳,所以不需要使用关键部分。
    只调用一次QueryPerformanceFrequency,因为在系统运行时频率不会改变。
    应用兼容性
    许多开发人员多年来一直对RDTSC的行为做出假设,因此,由于时序实现,一些现有应用程序很可能在具有多个处理器或内核的系统上运行时会出现问题。这些问题通常表现为毛刺或慢动作。对于不了解电源管理的应用程序没有简单的补救措施,但是现有的填充程序可以强制应用程序始终在多处理器系统中的单个处理器上运行。
    要创建此填充程序,请从Windows应用程序兼容性下载Microsoft应用程序兼容性工具包。
    使用兼容性管理器(工具包的一部分),创建应用程序和相关修订的数据库。为此数据库创建新的兼容性模式,并选择兼容性修补程序SingleProcAffinity以强制应用程序的所有线程在单个处理器/核心上运行。通过使用命令行工具Fixpack.exe(也是工具包的一部分),您可以将此数据库转换为可安装的软件包以进行安装,测试和分发。
    有关使用兼容性管理器的说明,请参阅工具包的文档。有关使用Fixpack.exe的语法和示例,请参阅其命令行帮助。
    有关面向客户的信息,请参阅Microsoft帮助和支持中的以下知识库文章:

    使用用例:

    LARGE_INTEGER  large_interger;
    QueryPerformanceCounter(&large_interger);
    c1 = large_interger.QuadPart;
    

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • java中实现定时器的三种方式,qutarz+spring, spring-task, java Timer
  • SpringBoot整合定时器几种方式

    千次阅读 2019-06-06 14:17:32
    实现定时器方式有 1.TimerTask类提供的定时方法 2.Quartz定时器 3.@Scheduled注解方式 1.TimerTask类提供的定时方法 该方法可直接运行类查看效果 public static void main(String[] args) throws java.io....
  • Swift定时器几种实现方式

    千次阅读 2018-06-06 15:09:54
    方式一:使用performSelector实现@objc func getCodeAction(sender:UIButton) { count = 60 self.performSelector(inBackground: #selector(timerThread), with: nil) //获取验证码 } @objc...
  • Android定时器的实现方式有以下几种,不同方式适用于不同的场合。
  • 实现定时器有很多种方式,在这里我简单的介绍几种方式 实现定时器有很多种方式,在这里我简单的介绍几种方式 (1)使用Handler + Runnable的方式 Handler handler = new Handler(); Runnable runnable =
  • 单片机C语言编程中,定时器的初值对于初学者真的是比较不好计算,因此我总结了以下几种方法。 第1种方法: #define FOSC 11059200L //晶振的频率 #define TIMS (65536-FOSC/12/1000) //12T mode 对于8051...
  • 本文为大家详细介绍下Android 定时器实现的几种方式:Handler + Runnable、Timer的方式、Handle与线程的sleep(long )方法和removeCallbacks失效问题如何解决
  • Android 定时器实现的几种方式和removeCallbacks失效问题详解.pdf
  • java定时器几种用法.pdf
  • 本篇文章主要介绍了详解Android实现定时器几种方法,主要包括了Handler, Timer, Thread, AlarmManager,兴趣的可以了解一下
  • 2.java中常见的定时器  1)借助Java.util.Timer来实现  2)OpenSymphony社区提供的Quartz来实现  3.介绍Timer  利用Timer开发定时任务是主要分为两个步骤:  1)创建定时任务类  ...
  • MCS-51单片机含有2个定时器/计数器,具有4种工作方式。具有两种工作模式(计数器模式和定时器模式)  MCS-51单片机含有1个全双工串行口,具有4种工作方式。  TMOD-》定时器/计数器方式控制寄存器  TCON-》...
  • 本文主要讲述了51定时器的3种工作方式
  • 实现定时器功能的几种方式

    千次阅读 2017-12-22 11:44:51
    首先会产生通知,corefunction向线程添加runloopObservers来监听事件,并控制NSRunLoop里面线程的执行和休眠,在事情做的时候使当前NSRunLoop控制的线程工作,没有事情做让当前NSRunLoop的控制的线程休眠。...
  • 定时器几种实现

    2020-06-13 14:35:08
    此篇文章为自己总结,若错麻烦指出,感谢 引言 服务器编程中一块是定时器,影响着服务器性能 定时器一个作用是用于定时检测客户端连接,并踢掉非活动连接; 定时器一般会把定时事件封装成定时器,并进行组织以方便...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 81,551
精华内容 32,620
关键字:

定时器有几种工作方式