提问者:小点点

没有无限while循环,还有其他方法实现“监听”功能吗?


我一直在思考像React这样的代码和库,它在事件发生时自动地对事件做出反应,我想知道所有这些是如何在C++和机器代码的较低级别上实现的。

我似乎找不出任何其他方法来实现像事件侦听器这样的东西,如果不是在另一个线程上运行while循环的话。

所以这一切都是隐藏在引擎盖下的吗?一直循环下去?比如RethinkDB,它将自己标榜为具有repubsub库的“实时数据库”。“subscribe”方法是否只是在引擎盖下使用while循环实现的?我似乎找不到任何有关这方面的信息。

插座之类的。当一台计算机在一个端口上“监听”套接字连接时,该计算机是否只是在运行类似于:

while(1) {
    if (connectionFound) {
        return True;
    }
}

还是我遗漏了什么?


共2个答案

匿名用户

我把这个问题的答案写在另一个答案中作为旁白。通常情况下,我会把这个问题作为一个复本来结束,然后指向那个答案,然而,这是一个非常不同的问题。另一个问题是关于javascript性能的。为了回答这个问题,我必须先写出这个问题的答案。

因此,我要做一件通常不应该做的事:我要把我的部分答案复制到另一个问题上。所以我的答案是:

javascript和Node.js等待的实际事件根本不需要循环。实际上,它们需要0%的CPU时间。

硬件

如果我们真的需要了解节点(或浏览器)内部是如何工作的,不幸的是,我们必须首先了解计算机是如何工作的--从硬件到操作系统。是的,这将是一次深潜,所以请容忍我。

这是一个伟大的发明,但也是一盒潘多拉-艾德斯格迪克斯特拉

是的,上面引用的是同一个“被认为有害的Goto”Dijkstra。从一开始,在计算机硬件中引入异步操作就被认为是一个非常困难的课题,即使对于业界的一些传奇人物来说也是如此。

引入中断是为了加快I/O操作。硬件将向CPU发送一个信号,告诉它发生了一个事件,而不是需要在无限循环中用软件轮询某些输入(占用CPU的有用工作时间)。然后CPU将挂起当前运行的程序,并执行另一个程序来处理中断--因此我们将这些函数称为中断处理程序。而且“处理程序”这个词一直在堆栈上延伸到将回调函数称为“事件处理程序”的GUI库。

维基百科实际上有一篇关于中断的相当不错的文章,如果您不熟悉它并且想了解更多的话:https://en.Wikipedia.org/wiki/interrupt。

如果您一直在关注,您会注意到中断处理程序的概念实际上是一个回调。您将CPU配置为在事件发生后的某个时间调用函数。因此,即使是回调也不是一个新概念--它比C要古老得多。

中断使现代操作系统成为可能。如果没有中断,CPU就没有办法暂时停止您的程序以运行OS(好吧,有合作多任务,但我们现在先忽略它)。OS的工作原理是它在CPU中设置一个硬件定时器来触发中断,然后它告诉CPU执行你的程序。就是这个周期性的计时器中断运行你的OS。

除了定时器,OS(或者更确切地说是设备驱动程序)为I/O设置中断。当I/O事件发生时,OS将接管您的CPU(或多核系统中的一个CPU),并根据其数据结构检查接下来需要执行哪个进程来处理I/O(这称为抢占式多任务)。

从键盘和鼠标到存储到网卡,所有东西都使用中断来告诉系统有数据要读取。如果没有那些中断,监视所有那些输入将占用大量CPU资源。中断是如此重要,以至于它们经常被设计成I/O标准,如USB和PCI。

既然我们对此有了清晰的了解,我们就可以理解Node/JavaScript实际上是如何处理I/O和事件的。

对于I/O,各种操作系统都有各种不同的API来提供异步I/O--从Windows上的重叠I/O到Linux上的poll/epoll到BSD上的kqueue到跨平台的select()。Node在内部使用libuv作为这些API的高级抽象。

这些API的工作方式是相似的,尽管细节不同。本质上,它们提供了一个函数,当调用该函数时,它将阻塞线程,直到操作系统向它发送事件。所以是的,即使是非阻塞I/O也会阻塞线程。这里的关键是,阻塞I/O会将线程阻塞在多个地方,而非阻塞I/O只将线程阻塞在一个地方--您等待事件的地方。

查看我对另一个问题的回答,了解这种API如何在C/C++级别上工作的更具体的示例:我知道回调函数是异步运行的,但为什么呢?

对于像按钮单击和鼠标移动这样的GUI事件,操作系统只需跟踪鼠标和键盘中断,然后将它们转换为UI事件。这使您的软件表单无需知道按钮,窗口,图标等的位置。

这允许你做的是以面向事件的方式设计你的程序。这类似于中断允许OS设计人员实现多任务的方式。实际上,异步I/O对于框架就像中断对于操作系统一样。它允许javascript花费正好0%的CPU时间来处理(等待)I/O。这就是异步代码快速的原因--它并不是真的更快,但不会浪费时间等待。

这个答案相当长,所以我会留下与这个主题相关的其他问题的答案的链接:

节点js体系结构和性能(注意:这个答案提供了一点关于事件和线程关系的见解-TLDR:OS在内核事件之上实现线程)

javascript是否使用弹性跑道算法进行处理

Node.js服务器如何优于基于线程的服务器

匿名用户

“听众”和“订阅”只是想法。所有的东西都可以用lambdas抽象出来。这里有一个可能的实现-

null

const logger =
  // create a new "listener",
  // send any data we "hear" to console.log
  listen(console.log)

// implement so-called "listener"
const listen = (responder) =>
  x => (responder(x), x)

// run it synchronously
logger(1)
logger(2)

// or asynchronously
setTimeout(_ => logger(3), 2000)

// 1
// 2
// some time later...
// 3