libuv 最初是为 Node.js 所做的跨平台库。它基于事件驱动的异步 I/O 模型。html
libuv 不只仅只提供了对于不一样 I/O 轮询机制的简单抽象:“句柄(handles)”和“流(streams)”也提供了对于 socket 和其余相关实例的高度抽象。同时 libuv 还提供了跨平台文件 I/O 接口和多线程接口等等。缓存
下图展现了 libuv 的不一样组成部分,以及与这些部分相关的子模块:安全
为了能使用户介入事件循环(event loop),libuv 为用户提供了两个抽象:句柄和请求。服务器
句柄表示一个在其被激活时能够执行某些操做且持久存在的对象。例如:当一个预备句柄(prepare handle)处于激活时,它的回调函数会在每次事件循环中被调用;每当一个新 TCP 链接来到时,一个 TCP 服务器句柄的链接回调函数就会被调用。网络
请求(一般)表示一个短暂存在的操做。这些操做能够操做于句柄,例如写请求(write requests)用于向一个句柄写入数据。可是又如 getaddrinfo 请求则不依赖于一个句柄,它们直接在事件循环上执行。多线程
事件循环是 libuv 的核心部分。它为全部的 I/O 操做创建了上下文,而且执行于一个单线程中。你能够在多个不一样的线程中运行多个事件循环。除非另有说明,否则 libuv 的事件循环(以及其余循环或句柄提供的 API)并非线程安全的。异步
事件循环遵循着广泛的单线程异步 I/O 行为:全部的(网络)I/O 体如今非阻塞的 socket 上,对于不一样的平台,libuv 会选取最佳的轮询机制:Linux 上为 epoll ,OSX 和其余 BSD 上为 kqueue ,SunOS 上为 event ports , Windows 上则为 IOCP 。做为循环迭代的一部分,事件循环会阻塞并等待被添加的 socket 上 I/O 活动的发生。而后根据当前的 socket 状况(可读,可写,挂起)触发相应的回调函数。因此,一个句柄是能够执行读操做,写操做或其余 I/O 行为。socket
为了能更好的理解事件循环是如何工做的,下图展现了事件循环一次迭代的全部过程:async
事件循环中的“如今时间(now)”被更新。事件循环会在一次循环迭代开始的时候缓存下当时的时间,用于减小与时间相关的系统调用次数。函数
若是事件循环还是存活(alive)的,那么迭代就会开始,不然循环会马上退出。若是一个循环内包含激活的可引用句柄,激活的请求或正在关闭的句柄,那么则认为该循环是存活的。
执行超时定时器(due timers)。全部在循环的“如今时间”以前超时的定时器都将在这个时候获得执行。
执行等待中回调(pending callbacks)。正常状况下,全部的 I/O 回调都会在轮询 I/O 后马上被调用。可是有些状况下,回调可能会被推迟至下一次循环迭代中再执行。任何上一次循环中被推迟的回调,都将在这个时候获得执行。
执行闲置句柄回调(idle handle callbacks)。尽管它有个不怎么好听的名字,但只要这些闲置句柄是激活的,那么在每次循环迭代中它们都会执行。
执行预备回调(prepare handle)。预备回调会在循环为 I/O 阻塞前被调用。
开始计算轮询超时(poll timeout)。在为 I/O 阻塞前,事件循环会计算它即将会阻塞多长时间。如下为计算该超时的规则:
若是循环带着 UV_RUN_NOWAIT
标识执行,那么超时将会是 0 。
若是循环即将中止(uv_stop()
已在以前被调用),那么超时将会是 0 。
若是循环内没有激活的句柄和请求,那么超时将会是 0 。
若是循环内有激活的闲置句柄,那么超时将会是 0 。
若是有正在等待被关闭的句柄,那么超时将会是 0 。
若是不符合以上全部,那么该超时将会是循环内全部定时器中最先的一个超时时间,若是没有任何一个激活的定时器,那么超时将会是无限长(infinity)。
事件循环为 I/O 阻塞。此时事件循环将会为 I/O 阻塞,持续时间为上一步中计算所得的超时时间。全部与 I/O 相关的句柄都将会监视一个指定的文件描述符,等待一个其上的读或写操做来激活它们的回调。
执行检查句柄回调(check handle callbacks)。在事件循环为 I/O 阻塞结束后,检查句柄的回调将会马上执行。检查句柄本质上是预备句柄的对应物(counterpart)。
执行关闭回调(close callbacks)。若是一个句柄经过调用 uv_close()
被关闭,那么这将会调用关闭回调。
尽管在为 I/O 阻塞后可能并无 I/O 回调被触发,可是仍有可能这时已经有一些定时器已经超时。若事件循环是以 UV_RUN_ONCE
标识执行,那么在这时这些超时的定时器的回调将会在此时获得执行。
迭代结束。若是循环以 UV_RUN_NOWAIT
或 UV_RUN_ONCE
标识执行,迭代便会结束,而且 uv_run()
将会返回。若是循环以 UV_RUN_DEFAULT
标识执行,那么若是若它仍是存活的,它就会开始下一次迭代,不然结束。
重要:虽然 libuv 的异步文件 I/O 操做是经过线程池实现的,可是网络 I/O 老是在单线程中执行的。
注意:虽然在不一样平台上使用的轮询机制不一样,但 libuv 的执行模型在不一样平台下都是保持一致。
与网络 I/O 不一样,并不存在 libuv 能够依靠的各特定平台下的文件 I/O 基础函数,因此目前的实现是在线程中执行阻塞的文件 I/O 操做来模拟异步。
更多关于跨平台异步文件 I/O 操做的内容,可参阅此文。
libuv 目前使用了一个全局的线程池,全部的循环均可以往其中加入任务。目前有三种操做会在这个线程池中执行:
文件系统操做
DNS 函数(getaddrinfo 和 getnameinfo)
经过 uv_queue_work()
添加的用户代码
注意:更多关于 libuv 线程池的信息请参阅此文。请牢记线程池的大小是有限的。