libuv 是一个专注于异步 I/O 的多平台支持库。它最初是为 Node.js 开发的,但也被 Luvit、Julia、uvloop 等平台使用。
libuv 是一个跨平台支持库,最初是为 Node.js 编写的。它围绕事件驱动的异步 I/O 模型进行设计。
该库提供的不仅仅是对不同 I/O 轮询机制的简单抽象:“句柄”和“流”为套接字和其他实体提供了高级抽象;此外,它还提供了跨平台文件 I/O 和线程功能等。
下图展示了 libuv 的各个组成部分以及它们与哪些子系统相关:

libuv 为用户提供了两种抽象概念,它们与事件循环结合使用:句柄和请求。句柄表示长期存在的对象,能够在活动状态下执行某些操作。以下是一些示例:
请求表示(通常)短期操作。这些操作可以通过句柄执行:写入请求用于将数据写入句柄;也可以独立执行:getaddrinfo 请求不需要句柄,它们直接在循环中运行。
I/O(或事件)循环是 libuv 的核心部分。它负责所有 I/O 操作的内容,并且应该绑定到单个线程。可以运行多个事件循环,只要每个循环在不同的线程中运行即可。除非另有说明,否则 libuv 事件循环(或任何其他涉及循环或句柄的 API)都不是线程安全的。
事件循环遵循相当常见的单线程异步 I/O 方法:所有(网络)I/O 都在非阻塞套接字上执行,这些套接字使用特定平台上可用的最佳机制进行轮询:Linux 上的 epoll、OSX 和其他 BSD 上的 kqueue、SunOS 上的事件端口以及 Windows 上的 IOCP。作为循环迭代的一部分,循环将阻塞等待已添加到轮询器的套接字上的 I/O 活动,并触发回调指示套接字状态(可读、可写挂断),以便句柄可以读取、写入或执行所需的 I/O 操作。
为了更好地理解事件循环如何运作,下图说明了循环迭代的所有阶段:

循环的“now”概念最初已设置。
如果循环使用 UV_RUN_DEFAULT 运行,则会运行到期计时器。所有在循环“now”概念之前调度的活动计时器都会被调用其回调。
如果循环处于活动状态,则启动迭代,否则循环将立即退出。那么,什么时候循环被认为是活动的呢?如果循环具有活动且已引用的句柄、活动请求或正在关闭的句柄,则被视为活动状态。
待处理的回调会被调用。大多数情况下,所有 I/O 回调都会在轮询 I/O 后立即调用。然而,在某些情况下,此类回调的调用会被推迟到下一次循环迭代。如果上一次迭代推迟了任何 I/O 回调,它将在此时运行。
空闲句柄回调会被调用。尽管名称不太好理解,但如果空闲句柄处于活动状态,它们会在每次循环迭代中运行。
准备句柄回调会被调用。准备句柄会在循环因 I/O 阻塞之前调用其回调。
轮询超时会进行计算。在因 I/O 阻塞之前,循环会计算应阻塞的时间。计算超时的规则如下:
循环阻塞 I/O。此时,循环将阻塞 I/O,阻塞时间长度为上一步计算得出的时长。所有与 I/O 相关的、正在监视给定文件描述符以进行读写操作的句柄,此时都会调用其回调函数。
检查句柄回调函数会被调用。检查句柄的回调函数会在循环阻塞 I/O 后立即被调用。检查句柄本质上是准备句柄的对应函数。
关闭回调函数会被调用。如果句柄是通过调用 uv_close() 关闭的,则会调用其关闭回调函数。
更新了循环中“now”的概念。
到期计时器开始运行。请注意,“now”函数在下一次循环迭代之前不会再次更新。因此,如果某个计时器在其他计时器正在处理时到期,它将在下一次事件循环迭代之前运行。
迭代结束。如果循环以 UV_RUN_NOWAIT 或 UV_RUN_ONCE 模式运行,则迭代结束,uv_run() 将返回。如果循环以 UV_RUN_DEFAULT 运行,则如果循环仍然存在,它将从头继续,否则它也会结束。
重要提示
libuv 使用线程池来实现异步文件 I/O 操作,但网络 I/O 始终在单个线程(每个循环的线程)中执行。
libuv 采用 MIT 许可证。