名词的大规模普及是在Web 2波,这是伴随着由Javascript和Ajax席卷网络,但在大多数高级语言编程时不common.php最能反映这一特征:它不仅保护异步多线程,甚至不提供,和PHP是一个同步的方式执行。这一优势为了帮助程序员编写业务逻辑,但在复杂的网络应用,阻断导致它不能更好的并发性。

在服务器端,我 / O是非常昂贵和分布式I / O是更昂贵,只有后端可以快速响应资源,与前端的经验可以better.node.js是第一平台采取异步作为主要的编程方法和设计理念。通过异步I/O、事件驱动和单线程,形成节点的基调,本文介绍了节点如何实现异步I/O。

1。基本概念

异步和非阻塞的声音是一样的,它们都达到了实际效果的并行的目标,但是从计算机内核I/O,只有两种阻塞方式和非阻塞方式,所以异步/同步和阻塞/非阻塞实际上是两种不同的东西。

1.1阻塞I/O和非阻塞I/O

阻塞I/O的一个特性是,调用必须等到系统内核级的所有操作完成之后,调用才能完成。作为读取磁盘上文件的示例,当系统内核完成磁盘搜索、读取数据并将数据复制到内存中时,调用结束。

阻挡我/ O导致CPU等待我/ O,浪费等待时间,和CPU的处理能力不能充分利用,非阻塞我 / O的特点是,电话将电话后立即返回,和CPU时间片可以用来处理其他交易。因为完整的我/ O没有完成,立即返回不是业务层的预期数据,但只有当前呼叫状态。为了获得完整的数据,应用程序需要重复我 / O操作确认是否完成(即投票)。投票方式如下:

1.read:这是最原始的方式来检查我/ O状态的重复音

2.select:阅读的改进是通过文件描述符上的事件状态来判断。缺点是文件描述符的最大数目是有限的

3.poll:提高选择的链表是用来避免限制的最大数目,但性能仍然很低,当描述符更。

4.epoll:如果我/ O事件不进的投票,将休眠直到事件发生。这是最有效的我/在当前Linux操作系统事件通知机制

轮询满足非阻塞I/O的要求,以确保获取完整的数据,但对于应用程序,它只能被视为同步,因为I/O仍然需要等待完整的返回。在等待期间,CPU要么用来遍历文件描述符的状态,要么等待休眠事件发生的等待。

理想与现实中的1.2异步I/O

完美的异步I/O应该是启动非阻塞调用的应用程序。它可以在没有轮询的情况下直接处理下一个任务。只有在I/O完成后,它才能通过信号或回调将数据传递给应用程序。

在现实中,异步I / O的不同操作系统下的不同实现,例如*nix平台使用一个自定义的线程池,和Windows平台使用IOCP模型,节点提供了一个libuv作为一个抽象封装层封装平台兼容性的判断,并保证上节和下平台独立的异步I / O实现。更重要的是,我们应该强调节点是单线程。这意味着Javascript的执行是单线程的,实际上I/O是在节点中完成的。

2。节点异步I/O

2.1活动周期

节点的执行模型实际上是一个事件循环,在一个进程的开始时,节点创建一个无限循环,循环的每一次执行都变成一个滴答,每个滴答过程是为了确定是否有等待处理的事件。如果存在,则提取事件和相关回调函数。如果有相关回调函数,执行它们并输入下一个循环。如果没有更多的事件处理,则退出进程。

2.2个观察员

有一些在每个活动周期的观察者,通过这些观察以确定是否有一个事件来处理。事件的周期是一个典型的生产者/消费者模式,节点,事件主要来自网络的请求,文件我 / O,等这些事件都有相应的网络我/ O观察员,文件,我 / O观察员,等事件循环发生的事件从观察和处理。

2.3请求对象

有一个中间产物称为从Javascript调用内核的转换请求对象执行的我/ O操作。以最简单的Windows,这fs.open()方法用于打开一个文件,根据指定的路径和参数,得到一个文件描述符为例,从JS调用内置模块,系统调用实际上是通过libuv,,的uv_fs_open()方法被调用,调用过程中,一个fsreqwrap请求对象的创建,以及参数和方法从JS层封装在对象的介绍。回调函数我们最关心的是对对象的oncompete_sym属性。当物体被包裹的物体,fsreqwrap推入线程池等待执行。

此时,js调用立即返回,JS线程可以继续执行后续操作。当前的I/O操作在在线池中等待执行,它完成异步调用的第一阶段。

2.4执行回调

回调通知是异步的第二阶段我/ o.after的我/ O操作线程池中完成,它将存储结果,然后通知IOCP,当前对象的操作完成,线程返回线程池。在执行期间,我 /事件循环的观测器O调用相关的方法来检查是否有任何执行请求的线程池。如果存在,它会将请求对象加入到I/O观察队列中,然后将其视为事件处理。



三.非I/O异步api

也有一些异步API是独立于我/ O节点,如定时器setTimeout(),setInterval(),process.nexttick,和process.nexttick(),等等。

3.1定时器API

setTimeout和setInterval()()API,在浏览器端是一致的,它们的实现类似于异步I / O,只是没有对我/ O线程池的参与。调用API来创建一个定时器,定时器将插入红黑树的观察者,事件的每个周期将蜱从红黑树的定时器对象删除迭代,检查是否随着时间的推移,如果超过一个事件的形成,回调函数是立即执行。定时器的一个主要问题是它的时间不是特别准确(毫秒,在可容忍的范围内)。

3.2立即异步执行任务API

在节点出现之前,许多人可能会调用,以便立即异步地执行任务。

复制代码代码如下所示:

setTimeout(){()函数(



},0);



由于该事件的周期特点,定时器的精度是不够的,和红黑树的使用是对定时器的使用要求和操作的时间复杂度为O(log(n)),process.nexttick()方法只会把回调函数为队列中取出执行在剔下一轮,其计算复杂度为O(1)更有效。

此外,一个setimmediate()方法与此类似,都是要延迟回调函数。但前者比后者有更高的优先级,因为事件周期的观察顺序检查。此外,前者的回调函数存储在一个数组。每一轮跳将数组中执行所有的回调函数,后者是保存在链表。每个回合将执行回调函数。

4。事件驱动与高性能服务器

在前面的例子中,fs.open()作为节点如何实现异步我/组织事实的一个例子,网络套接字的处理,Node还应用异步I / O,这是节点建立一个Web服务器上的服务器模型是经典:

1。同步:一次只能处理一个请求,其余请求处于等待状态。

2。每一个进程请求:每个请求都启动一个进程,但是系统资源是有限的,并且没有可用的扩展性。

三.每个线程请求:每个请求都启动一个线程。线程比进程轻,但每个线程占用一定数量的内存。当大型并发请求到来时,内存将很快被使用。

著名的Apache以每个线程请求的形式使用,这也是它难以处理高并发性的原因,节点通过事件驱动方式处理请求,从而节省了创建和销毁线程的成本。同时,在调度任务时,操作系统的线程成本较低,上下文切换的成本也很低,即使在大量连接的情况下,节点也能以有序的方式处理请求。

著名的服务器Nginx也放弃了多线程,使用相同的事件驱动方式为节点。现在Nginx已经取代Apache的潜力。Nginx是纯C语言编写的,具有很高的性能,但它只适用于Web服务器的反向代理或负载平衡。节点可以建立相同的功能作为nginx的,可同时处理多种特定的服务,他们的表现很好。在实际工程中,我们可以结合他们的每一个点来实现应用的最佳性能。