Javascript的回调,不等同于MCU的中断

曾有一位做单片机开发的朋友问我回调是怎么回事,我解释就跟单片机的中断函数类似,比如你的定时器的溢出值设置为200毫秒,那么单片机内部电路每200毫秒,就会把PC设置成你的定时器中断向量入口,达到调用中断程序的目的。在浏览器上,JS引擎负责做这个事,回调函数就相当于ISR。

后来一想,这种说法有问题。只怪我当时对Javascript的特性不是很了解。表面上看它很恰当,但是却让人忽略了一些重要的问题。MCU的中断,是那种会直接打断程序流程的东西,而Javascript的回调,会很绅士地一直等下去。

MCU是怎样的?

还是拿「定时器中断」来说,当定时器的计数器溢出或达到阈值,就会改变内部一个标志位。正在运行的指令执行完后,这个中断标志就会被处理,不同的MCU会有不同的处理方式。

大致原则是:

  • 除非正在处理更高优先级的中断,否则立即响应定时器中断,调用中断处理子程序。
  • 当正处在其他不可打断的中断(可能是优先级高,也可能是CPU本来就不支持打断),那么就等着,那个一完,这个就立即响应。

也就是说,基本上MCU会立即响应!

单片机工程师在这一块会非常小心,举个例子,当MCU字长小于数据的字长,比如在8位机上使用int,如果这个值碰巧也会被ISR修改,就需要在处理int前,先关掉全局中断,处理完了重新打开。因为你不知道什么时候中断会产生并打断你。

出于类似的原因,软件开发人员编写多线程程序时,经常需要「加锁」。

Javascript是怎样的?

我们拿JS的setTimeOut来做个实验。(正好和定时器中断做个对比)

setTimeout(() => console.log("cool"), 1000)

这个会在1秒钟后,打印出一个“cool”。一切在预期中,与「MCU中断」行为类似。

举其他的例子之前,我们先定义一个函数,通过死循环来达到延时的效果。

function sleepSync(ms) {
  var mark = new Date().getTime()
  while (new Date().getTime() - mark < 4000)
}

再看另外一个例子

setTimeout(() => console.log("cool"), 1000)
sleepSync(4000)

出问题了,过了4秒之后才打印出“cool”。setTimeout并不能打断那个while死循环。

用其他的Javascript函数做个实验

// let's try to read a non-existing file
fs.readFile("/tmp/blah", (e, _) => console.log(e.message))
sleepSync(4000)

同样是过了4秒,才打印出

ENOENT: no such file or directory, open '/tmp/blah'

所以JS的「回调」和「MCU的中断」,是不能等同的,它们有一些相似点,但是在关键的地方不太一样,所以以后还是不要打这个比方吧。

至于Javascript为什么是这个样子,这跟Javascript的Event Looptask queue有关了,但那是另外一个主题(如果感兴趣,请看另一篇博客),这篇文章只是纠正一下过去自以为很棒的比喻。

原文:http://madmuggle.me/articles/JSCallbackIsNotMCUInterrupt.html

    原文作者:madmuggle
    原文地址: https://www.jianshu.com/p/ac2d497d56c3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞