事件循环不会轮询,因此无法处理事件循环不会影响传入的事件。
事件循环的工作原理:
大多数现代操作系统(或类Unix古老操作系统)在操作系统级别而不是应用程序级别处理I/O。 POSIX标准要求操作系统至少支持select()系统调用。select()函数是大多数程序用于处理非阻塞I/O的阻塞函数。这个语句听起来有矛盾之处,但实际上并不是。
非阻塞I/O的工作原理:
我将使用select()作为例子,但不同的操作系统也有其他非阻塞API,如poll()和epoll()和重叠-I/O(Windows)。各种JavaScript引擎通常使用像libuv这样的库,在编译时自动处理要使用哪个API。
非阻塞API通常提供一个函数,比如select(),它会阻塞并等待应用程序正在侦听的任何I/O上的事件。为什么是阻塞的?因为这是程序使用0%CPU时间的唯一方法。否则,进程将忙于轮询,这将非常低效。
块式I/O是指任何告诉操作系统“嘿,我在等待这个‘事情’,所以你可以将我从CPU共享计划中移除,并且仅在‘事情’到来时唤醒我的任何功能。”非块式I/O和块式I/O的区别在于非块式I/O在等待多个I/O时会阻塞,而块式I/O在等待单个I/O时会阻塞。如果想了解更多,请查询“select()”POSIX函数的文档。无论如何,JavaScript使用非块式I/O,因此在读取I/O时不会阻塞,但在“select()”或类似的函数上会阻塞。当解释器执行JavaScript代码时,显然不会同时调用“select()”函数。因此,当解释器忙碌时,操作系统会缓冲程序的任何I/O。
操作系统轮询吗?
不。通常操作系统不会轮询(当然这取决于设备驱动程序,但一般来说不会)。I/O活动是由中断处理的。即使对于非中断驱动的I/O(例如USB),一般处理该I/O的芯片组在其缓冲区满时会生成一个中断,以便操作系统将数据复制到RAM中的OS缓冲区。有时候对于高速设备,甚至不是操作系统进行复制,而是DMA控制器,一旦数据被复制到RAM中就会生成一个中断。
关于GUI活动呢?
最终,GUI活动如鼠标点击和按键也是中断驱动的(早期基于DOS的GUI管理器如Windows 1.0使用轮询驱动的鼠标驱动程序,后来微软看了Mac OS的演示,据传说苹果的一名工程师泄露了他们不会轮询的事实,自那时起,鼠标驱动程序通常会触发中断)。
例外情况:
一个小的例外是JavaScript中的线程。所谓线程,指的是浏览器中的Web Workers和Node.js中的磁盘I/O处理程序。例如,在Node.js中,磁盘I/O驱动程序是作为单个线程中的阻塞I/O实现的。因此,Node.js负责在将数据传回事件循环之前缓冲数据。同样,所有的操作系统缓冲层仍然存在:例如,在复制数据时,操作系统可能会在Node.js线程调用下一个
read()
之前缓冲完成的磁盘读取命令。无论如何,这些线程仍然通过I/O通道与事件循环进行通信,可以是管道、套接字或Unix域套接字,因此我上面概述的一切仍然成立:如果主JS线程忙碌,操作系统将简单地从线程缓冲数据(或者如果它是阻塞的,则线程将简单地阻塞,直到事件循环处理它们的I/O)。