文章译自 Ryan Zheng 所写的 《Event Loop Distilled》[1]。
译者说:文章借助读取文件时的「同步」和「异步」两种模式来解释 Event Loop 到底要解决什么问题,并用伪代码的方式说明一个简单的 Event Loop 是如何实现的。最后,文章还猜测了 V8 引擎的 Event Loop 工作原理。
本文结合之前我翻译的 《深入探讨 Node.js 中的队列》,可以让大家对 Node.js 中 Event Loop 和 队列 有更完整的认识。

在了解什么是 Event Loop(事件循环) 之前,我们先了解它要解决什么问题。
我们先用一个例子来描述这个问题。
例子
当我们打开一个文件进行读取时,如果是同步(synchronously)地读取内容,当前线程会被阻塞,直到读取完成。在读取一个文件时,有两种模式可以选择:同步和异步 ( synchronous and asynchronous )。
同步读取:操作系统内核(OS kernel)会将当前线程置于等待状态(wait state),在内核将文件读取完成并将读取的数据放入文件描述符缓冲区后(file descriptor buffer),内核会唤醒这个线程。以便让线程继续运行。
异步读取:当操作系统内核看到文件读取模式是异步的,它就会立即返回,当前线程可以继续运行。操作系统内核可能在IO队列中放了一个文件读取请求。这对我们来说并不重要。
在服务端应用中,服务器要服务于许多客户端,这意味着会同时出现许多 sockets 连接。旧的处理方式是,每个 socket 连接都占用一个线程。然而每个进程所允许的线程数量是有限的,而且线程本身会耗费内存和资源。
这些资源包括线程数据结构 (thread data structure)、分页内存 (paging memory)、上下文切换 (context switching) 等。现代的方法是,让每个 socket 变成异步的。并把这些 socket 放在一个全局的地方管理,然后不断的检查这些 socket。比如问每一个 socket "你有数据可用吗"。不同的操作系统提供了不同的检查这些 socket 的方式。在 Linux 上,有 select、epoll 等。在 windows 上,可能有所不同。
伪代码
global List<Map<FileDescriptor,Callback> queue;
class EventLoop {
while(queue.isNotEmpty())
{
event = queue.next();
if(event.fd.ready())
{
event.callback(event.fd.data)
}
}
}
整个思路就像上面的伪代码,不过通常而言 server socket 需要存活很久。所以我们必须使用一些方式来保证 while 循环不会一直占用 CPU 时钟 (tick)。采取的方式取决于系统函数。许多系统函数提供了对批量的文件描述符进行等待的功能。这些系统函数只有在其中一个文件描述符有数据时才会返回。
wait(events: Set<FileDescriptor>) {
/*
when one file descritpor has data, wait will return.
Wait does not continually ticking the cpu.
*/
}
在 Node.js 中,他们使用 libuv 来创建 Event Loop。Event Loop 基本上就像一个注册 <FileDescriptor,Callback>
组合的地方。
event_loop 有一个 run 函数,它等待某一个文件描述符变成 ready 状态。当 ready 发生时,它就会安排一个回调任务。
V8 Engine
V8 引擎用来执行 JavaScript 代码。我没有看过 V8 引擎的源码,但是 V8 引擎也是用 Event Loop 来处理事件的。它说如果一个回调运行时间长,就会阻塞 Event Loop。如果用我们前述的 Event Loop 模型,V8 也是使用 event loop while 来检查文件描述符。当它看到其中一个文件描述符中有数据时,它将调用回调。所以如果回调比较耗时,那么Event Loop 线程在执行函数时就会处于忙碌状态,无法继续检查其他事件。在 V8 引擎中,其实有两个队列,macrotask 和 microtasks 。在 Event Loop 的每次迭代 (iteration) 中,只会执行一个 macrotask,而所有的 microtask 都会从 microtask 队列中拿出来执行。
参考资料
Event Loop Distilled: https://ryan-zheng.medium.com/event-loop-distilled-3a47b9923c66




