定时器杂谈

291 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

定时器的概念

比如说,大家写前端的时候,几乎都使用过一个函数:

setTimeout(函数, 等待的毫秒数)

这样可以让我们在一个具体的时间之后,再去执行某一个函数的代码,从而做到在时间上来控制代码,比如说,一秒之后,销毁某个特效。

这种东西就是定时器。

不用setTimeout,能实现上述功能吗?

这里讨论的上下文环境,不限定在前端浏览器里,而是一个通用的概念。如何实现上述功能。尤其是,不使用比较底层的,或者某语言标准库里现成的定时函数,能不能上述功能?

从单片机里定时器的实现说起

单片机里一般没有比较现成而高级的定时器函数,所以研究单片机的实现,可以打开思路:

搞一个死循环,由于单片机cpu比较慢,所以大概每一次循环所需要的时间是一个相对大的值,比如说100次循环,就要花费一秒钟,那么,休眠一秒钟,完全可以用下面的代码实现:

int count = 0;
while(count < 100){++count;}

在休眠一秒之后,再去执行业务代码,这就是相当于实现了一个定时器。

无限循环,读取时间戳

单片机里的实现给了我们一个思路,那就是无限循环。

我们就有了这样的办法:

  • 首先存储一下现在的时间戳存入 time_begin 变量。
  • 搞一个无限循环,循环里的逻辑就是,拿当前的时间戳与 time_begin 对比,如果已经超过某个值了,说明我们预计的时间到了

大概的代码如下:

var time_begin = getSystemTime();
while(true)
{
    if (getSystemTime() - time_begin > 1) // 超过 1 秒
    {
        // 执行某个业务逻辑
    }
}

上面的代码可以让我们比较精确的在一秒后去执行某个业务逻辑。

无限循环的问题

即使循环体里面只有一个小小的比较时间戳的逻辑,但这种代码也是极其耗费CPU的。

而且在前端来说,由于业务逻辑代码,是单线程的,所以这种写法会卡住整个界面,不信可以在console里试试。

还有一个缺点就是,无法同时进行多个定时器,因为某一个定时器任务需要一直卡住,直到这个定时器的时间就绪,然后执行业务代码,然后退出循环,才能轮到后面的定时器。

不过,我们在循环里,可以同时判断所有的定时器,但是这个循环本身还是会一直卡住,整个前端界面,除了定时判断,什么也做不了。

无限循环方案在什么情况下可行

有两种情况下,无限循环方案是可行的:

    1. 无限循环不会消耗cpu
    1. 本身的业务逻辑已经包含一个大的循环

先说第一种情况,在Linux里,有一个东西叫Epoll,Epoll功能有一个函数,叫epoll_wait, 这是一个等待的函数,这个等待本身是为了等待某些IO是就绪的,不过epoll_wait有一个参数,可以控制超时时间。

在纯定时器情况下,我们可以将我们的定时器的超时时间设置进去,从而达到不卡cpu。

再说第二种情况,对游戏引擎熟悉的同学,应该知道,游戏引擎本身就是一个无限循环,所以在这个本身的无限循环里,加上述的定时逻辑,是相当可行的。