澳门皇冠金沙网站-澳门皇冠844网站

热门关键词: 澳门皇冠金沙网站,澳门皇冠844网站

O学习笔记

“异步”这个名词的大规模流行是在Web 2.0浪潮中,它伴随着Javascript和AJAX席卷了Web。但在绝大多数高级编程语言中,异步并不多见。PHP最能体现这个特点:它不仅屏蔽了异步,甚至连多线程也不提供,PHP都是以同步阻塞的方式来执行。这样的优点利于程序猿顺序编写业务逻辑,但在复杂的网络应用中,阻塞导致它无法更好地并发。

Node.js异步I/O学习笔记,node.js学习笔记

“异步”这个名词的大规模流行是在Web 2.0浪潮中,它伴随着Javascript和AJAX席卷了Web。但在绝大多数高级编程语言中,异步并不多见。PHP最能体现这个特点:它不仅屏蔽了异步,甚至连多线程也不提供,PHP都是以同步阻塞的方式来执行。这样的优点利于程序猿顺序编写业务逻辑,但在复杂的网络应用中,阻塞导致它无法更好地并发。

在服务器端,I/O非常昂贵,分布式I/O更加昂贵,只有后端能快速响应资源,前端的体验才能变得更好。Node.js是首个将异步作为主要编程方式和设计理念的平台,伴随着异步I/O的还有事件驱动和单线程,它们构成Node的基调。本文将介绍Node是如何实现异步I/O的。

1. 基本概念

“异步”与“非阻塞”听起来似乎是一回事,从实际效果而言,这两者都达到了并行的目的。但是从计算机内核I/O而言,只有两种方式:阻塞与非阻塞。因此异步/同步和阻塞/非阻塞实际上是两回事。

1.1 阻塞I/O与非阻塞I/O

阻塞I/O的一个特点是调用之后一定要等到系统内核层面完成所有操作后,调用才结束。以读取磁盘上的一个文件为例,系统内核在完成磁盘寻道、读取数据、复制数据到内存中后,这个调用才结束。

阻塞I/O造成CPU等待I/O,浪费等待时间,CPU的处理能力不能得到充分利用。非阻塞I/O的特点就是调用之后会立即返回,返回后CPU的时间片可以用来处理其他事务。由于完整的I/O并没有完成,立即返回的并不是业务层期待的数据,而仅仅是当前调用的状态。为了获取完整的数据,应用程序需要重复调用I/O操作来确认是否完成(即轮询)。轮询技术要以下几种:

1.read:通过重复调用来检查I/O状态,是最原始性能最低的一种方式
2.select:对read的改进,通过对文件描述符上的事件状态来进行判断。缺点是文件描述符最大的数量有限制
3.poll:对select的改进,采用链表的方式避免最大数量限制,但描述符较多时,性能还是十分低下
4.epoll:进入轮询时若没有检查到I/O事件,将会进行休眠,直到事件发生将其唤醒。这是当前Linux下效率最高的I/O事件通知机制

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

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

完美的异步I/O应该是应用程序发起非阻塞调用,无需通过轮询就可以直接处理下一个任务,只需在I/O完成后通过信号或回调将数据传递给应用程序即可。

现实中的异步I/O在不同操作系统下有不同的实现,如*nix平台采用自定义的线程池,Windows平台采用IOCP模型。Node提供了libuv作为抽象封装层来封装平台兼容性判断,并保证上层Node与下层各平台异步I/O的实现各自独立。另外需要强调的是我们经常提到Node是单线程的,这仅仅是指Javascript的执行在单线程中,实际在Node内部完成I/O任务的都另有线程池。

2. Node的异步I/O

2.1 事件循环

Node的执行模型实际上是事件循环。在进程启动时,Node会创建一个无限循环,每一次执行循环体的过程成为一次Tick。每个Tick过程就是查看是否有事件等待处理,如果有则取出事件及其相关的回调函数,若存在关联的回调函数则执行它们,然后进入下一个循环。如果不再有事件处理,就退出进程。

2.2 观察者

每个事件循环中有若干个观察者,通过向这些观察者询问来判断是否有事件要处理。事件循环是一个典型的生产者/消费者模型。在Node中,事件主要来源于网络请求、文件I/O等,这些事件都有对应的网络I/O观察者、文件I/O观察者等,事件循环则从观察者那里取出事件并处理。

2.3 请求对象

从Javascript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,叫做请求对象。以最简单的Windows下fs.open()方法(根据指定路径和参数去打开一个文件并得到一个文件描述符)为例,从JS调用到内建模块通过libuv进行系统调用,实际上是调用了uv_fs_open()方法。在调用过程中,创建了一个FSReqWrap请求对象,从JS层传入的参数和方法都封装在这个请求对象中,其中我们最为关注的回调函数被设置在这个对象的oncompete_sym属性上。对象包装完毕后,将FSReqWrap对象推入线程池中等待执行。

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

2.4 执行回调

回调通知是异步I/O的第二阶段。线程池中的I/O操作调用完毕后,会将获取的结果储存起来,然后通知IOCP当前对象操作已完成,并将线程归还线程池。在每次Tick的执行中,事件循环的I/O观察者会调用相关的方法检查线程池中是否有执行完的请求,如果存在,会将请求对象加入到I/O观察者的队列中,然后将其当做事件处理。

图片 1

3. 非I/O的异步API

Node中还存在一些与I/O无关的异步API,例如定时器setTimeout()、setInterval(),立即异步执行任务的process.nextTick()和setImmdiate()等,这里略微介绍一下。

3.1 定时器API

setTimeout()和setInterval()浏览器端的API是一致的,它们的实现原理与异步I/O类似,只是不需要I/O线程池的参与。调用定时器API创建的定时器会被插入到定时器观察者内部的一棵红黑树中,每次事件循环的Tick都会从红黑树中迭代取出定时器对象,检查是否超过定时时间,若超过就形成一个事件,回调函数立即被执行。定时器的主要问题在于它的定时时间并非特别精确(毫秒级,在容忍范围内)。

3.2 立即异步执行任务API

在Node出现之前,很多人也许为了立即异步执行一个任务,会这样调用:

复制代码 代码如下:

setTimeout(function() {
    // TODO
}, 0);

由于事件循环的特点,定时器的精确度不够,而且采用定时器需要使用红黑树,各种操作时间复杂度为O(log(n))。而process.nextTick()方法只会将回调函数放入队列中,在下一轮Tick时取出执行,复杂度为O(1)更为高效。

此外还有一个setImmediate()方法和上述方法类似,都是将回调函数延迟执行。不过前者的优先级要比后者高,这是因为事件循环对观察者的检查是有先后顺序的。另外,前者的回调函数保存在一个数组中,每轮Tick会将数组中的所有回调函数全部执行完;后者结果保存在链表中,每轮Tick只会执行一个回调函数。

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

前面以fs.open()为例阐述了Node如何实现异步I/O。事实上对网络套接字的处理,Node也应用了异步I/O,这也是Node构建Web服务器的基础。经典的服务器模型有:

1.同步式:一次只能处理一个请求,其余请求都处于等待状态
2.每进程/每请求:为每个请求启动一个进程,但系统资源有限,不具备扩展性
3.每线程/每请求:为每个请求启动一个线程。线程比进程要轻量,但每个线程都占用一定内存,当大并发请求到来时,内存很快就会用光

著名的Apache采用的就是每线程/每请求的形式,这也是它难以应对高并发的原因。Node通过事件驱动方式处理请求,可以省掉创建和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价也很低。即使在大量连接的情况下,Node也能有条不紊地处理请求。

知名服务器Nginx也摒弃了多线程的方式,采用和Node一样的事件驱动方式。如今Nginx大有取代Apache之势。Nginx采用纯C编写,性能较高,但是它仅适合做Web服务器,用于反向代理或负载均衡等。Node可以构建与Nginx相同的功能,也可以处理各种具体业务,自身性能也不错。在实际项目中,我们可以结合它们各自有点,以达到应用的最佳性能。

1.为什么要使用异步I/O

在服务器端,I/O非常昂贵,分布式I/O更加昂贵,只有后端能快速响应资源,前端的体验才能变得更好。Node.js是首个将异步作为主要编程方式和设计理念的平台,伴随着异步I/O的还有事件驱动和单线程,它们构成Node的基调。本文将介绍Node是如何实现异步I/O的。

Nodejs中有个Fibonacci的异步例子,疑问processnextTick()作用

你运行两个fibonacciAsync()就能看出是异步的了。  

1.1 用户体验

浏览器中的Javascripts是在单线程上执行的,并且和UI渲染公用一个线程。这就意味着在执行Javascript时候UI的渲染和响应是出于停滞的状态,如果脚本执行时间超过100ms用户就能感受到页面卡顿。在B/S模型中如果通过同步方式获取服务器资源Javascript需要等待资源的返回,这段时间UI将会停顿不响应交互。而采用异步方式请求资源的同时Javascript和UI渲染可以继续执行。

通过异步执行可以消除UI阻塞现象,但是获取资源速度取决于服务器的响应,假设有这么个场景,获取两个资源数据:

get('json_a');//需要消耗时间M
get('json_b');//需要消耗时间N

如果采用同步方式获取资源的时间为M N,如果采用异步方式时间则是max(M,N)。随着网站的扩大,数据将会分布在不同服务器上,分布式也将意味着M与N的值会线性增长。同步与异步的耗时差距也会变大。

1. 基本概念

程序员怎说服老板采用Nodejs?

导读:近期以来Node.js在业界很火,有关它的的新闻不胜枚举,种种迹象表明业界更多的公司在关注和考虑采用Node.js。俗话说“巧妇难为无米之炊”,程序员该如何成功说服老板听取您的建议?针对这一话题,作者Felix发表了一篇博文,文中分享了一些建设性指南,CSDN研发频道现将此文进行编译,分享给开发者,也欢迎大家发表自己Node.js实战心得。糟糕的使用案例Apps在CPU性能上的高使用率 尽管一直钟情于Node.js,但这里有几个使用案例,结果却并不令人如意。最明显的是Apps在CPU上的使用率以及I/O操作是极其高负荷的。因此,如果你打算写一个视频编码软件,人工智能或者类似CPU使用率比较高的软件,那么请不要使用Node.js,使用C或者C 效果会更好一些。话虽如此,但Node.js允许你轻松的编写C 插件,因此,你可以将它作为一个超级算法的脚本引擎。简单的CRUD/HTML AppsNode.js最终会成为一款不错的编写Web应用的工具。但是,你不能指望它能像PHP,Ruby,Python那样为你提供更多的好处。也许你的应用程序会因此而获得更多的可扩展性,但并不会因为用Node.js编写的而为你带来更多的访问量。当我们看到Node.js一些不错的框架时,或许你会因此而欣喜不已。事实上,至今还没有比Rails,CakePHP或者Django这些框架更具强大的应用功能。如果你的应用程序只是为了基于一些数据库给HTML做渲染,那么使用Node.js不会给你带来任何利益好处。NoSQL

  • Node.js 各种时髦词 假如你的下一个应用程序的系统架构读起来像NoSQL的配料菜谱,请花点时间阅读下面的内容。Redis,CouchDB,MongoDB,Riak,Casandra等这些看起来似乎很诱人,同样令人难以抗拒。如果你正在使用Node.js,那么就不应该附加上一些你完全不了解的技术。当然,也有选择一个文档数据库合理使用的案例。但是如果你想开发一个商业项目,请坚持保守的数据库技术(比如Postgres 或者 MySQL)或许能满足你的需求。出色的使用案例JSON APIs创建一个轻量级的REST / JSON API这确实是Node.js的一大亮点。如果需要包装其他的数据源(如数据库)或者Web服务器通过JSON接口让他们暴露出来,那么将非阻塞I/O模块与JavaScript结合在一起是个不错的选择。单一的页面应用如果你打算写一个AJAX单一的页面应用(如Gmail),Node.js非常适合。在极短的响应时间内获得更多的请求数,在客户端和服务器之间共享数据,为现代Web应用程序在客户端上做大量的处理,Node.js都能满足你的需求。Unix工具 Shelling out to unix tools目前Node.js还很年幼,它正试图为自己重新发明各类软件。不过更好的办法是深入到现有的广阔的命令行工具世界里。Node可以把这些成千上万的子进程以stream的方式输出,这也使它成为企业的理想选择。数据流Streaming data传统的Web栈将http请求和响应作为元事件处理。然而,他们是流动的,许多非常棒的Node.js应用程序正是利用这一优点创建的。这里有一个非常棒的案例,当进行实时解析上传文件时,还可以在不同的数据层之间创建代理。软件实时应用利用Node.js你可以轻松开发软件实时系统。比如Twitter、聊天工具,体彩或者即时通讯网络接口。但是,值得注意的是,因为JavaScript是一个动态的......余下全文>>  

“异步”这个名词的大规模流行是在Web 2.0浪潮中,它伴随着Javascript和AJAX席卷了Web。但在绝大多数高...

1.2 资源的分配

假设一组互不先关的任务需要执行,主流方法有两种:

  • 单线程串行依次执行
  • 多线程并行完成

如果创建多线程的开销小于并行执行,那么多线程的方式是首选。多线程的代价在于创建线程和执行线程时的上下文切换。在复杂业务中多线程需要面临锁、状态同步问题。优势在于多线程在多核CPU上可以提升CPU利用率。

单线程串行执行缺点在于性能,任意一个任务略慢都会影响下一个执行。通常I/O与CPU计算之间是可以并行进行的,但是同步编程导致I/O的进行会让后续任务等待,造成资源浪费。

Node在两者之间做出了自己的方案:利用单线程,远离多线程死锁、状态同步问题;利用异步I/O,让单线程远离阻塞,更好的利用CPU。

为了弥补单线程无法利用多核CPU缺点,Node提供了类似前端浏览器的Web Workers的子进程,子进程可以通过工作进程高效的利用CPU和I/O。

图片 2

异步I/O调用示意图

[异步I/O调用示意图]

“异步”与“非阻塞”听起来似乎是一回事,从实际效果而言,这两者都达到了并行的目的。但是从计算机内核I/O而言,只有两种方式:阻塞与非阻塞。因此异步/同步和阻塞/非阻塞实际上是两回事。

2.异步I/O实现

1.1 阻塞I/O与非阻塞I/O

2.1异步I/O与非阻塞I/O

操作系统内核对于I/O只有两种方式:阻塞和非阻塞。调用阻塞I/O时,程序需要等待I/O完成才返回结果,如图:

图片 3

调用阻塞I/O的过程

为了提高性能,内核提供了非阻塞I/O,非阻塞I/O调用之后会立刻返回,如图:

图片 4

调用非阻塞I/O的过程

非阻塞I/O返回后,完整的I/O并没有完成,立即返回的不是业务层期望的数据,仅仅是当前调用状态。为了获取完整的数据,应用需要反复调用I/O操作来确认是否完成。这种反复调用判断操作是否完成的计算叫做 轮询

现存的轮询技术主要有这些:

  1. read
    最原始的一种方式,通过反复调用I/O状态来完成数据读取,在获取最终数据前,CPU一直耗用在等待是,示意图:

图片 5

通过read进行轮询的示意图

  1. select
    在read基础上的改进方案,通过文件描述符上的事件状态来进行判断,select轮询有一个限制,它采用一个1024长度的数组来保存储存状态,所以它最多可以检查1024个文件描述符,示意图:

图片 6

通过select进行轮询示意图

  1. poll
    采用链表的方式来避免数组长度限制,能避免不需要的检查。当文件描述符多时,性能还是十分低下,于select相似,性能有所改善,如图:

图片 7

通过poll进行轮询示意图

  1. epoll
    Linux下效率最高的I/O事件通知机制,进入轮询时如果没有检查到I/O事件,将会进行休眠,直到事件将他唤醒。利用的事件通知、执行回调方式,而不是遍历查询,所以不会浪费CPU,执行效率比较高。示意图:

图片 8

通过epoll进行轮询示意图

  1. kqueue
    与epoll类似,只存在FreeBSD系统下。

阻塞I/O的一个特点是调用之后一定要等到系统内核层面完成所有操作后,调用才结束。以读取磁盘上的一个文件为例,系统内核在完成磁盘寻道、读取数据、复制数据到内存中后,这个调用才结束。

2.2 理想的非阻塞异步I/O

期望的完美异步I/O应该是程序发起非阻塞调用,无需通过遍历或者事件唤醒等轮询方式,可以进行下一个任务,只需要在I/O完成后通过信号或回调将数据传递给应用程序,示意图:

图片 9

理想异步I/O示意图

阻塞I/O造成CPU等待I/O,浪费等待时间,CPU的处理能力不能得到充分利用。非阻塞I/O的特点就是调用之后会立即返回,返回后CPU的时间片可以用来处理其他事务。由于完整的I/O并没有完成,立即返回的并不是业务层期待的数据,而仅仅是当前调用的状态。为了获取完整的数据,应用程序需要重复调用I/O操作来确认是否完成(即轮询)。轮询技术要以下几种:

2.3现实的异步I/O

通过让部分线程进行阻塞I/O或者非阻塞I/O加轮询技术完成数据获取。让一个线程进行处理计算,通过线程之间的通讯将I/O得到的数据进行传递,实现异步I/O,示意图:

图片 10

异步I/O

最初Node在*nix平台下采用libeio配合libev实现I/O异步I/O,Node v0.9.3中,自行实现了线程池完成异步I/O。
windows下通过IOCP来实现(实现原理仍然是线程池,只是由系统内核接受管理)。

windows和*nix平台的差异,Node提供了libuv作为封装,兼容性判断由这一层完成,Node编译期间会判断平台条件。

1.read:通过重复调用来检查I/O状态,是最原始性能最低的一种方式
2.select:对read的改进,通过对文件描述符上的事件状态来进行判断。缺点是文件描述符最大的数量有限制
3.poll:对select的改进,采用链表的方式避免最大数量限制,但描述符较多时,性能还是十分低下
4.epoll:进入轮询时若没有检查到I/O事件,将会进行休眠,直到事件发生将其唤醒。这是当前Linux下效率最高的I/O事件通知机制

3.Node的异步I/O

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

3.1事件循环

启动Node时会创建一个类似while(true)的循环,每执行一次循环过程称之为Tick。每个Tick的过程就是检查是否有待处理事件,如果有,就读取出事件及其相关的回调函数,如果存在关联的回调函数,就执行。然后加入下一个循环,如果不再有事件处理就退出进程。如图:

图片 11

Tick流程图

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

3.2观察者

在每个Tick过程中,怎么判断是否有事件需要处理呢?,这里引入了观察者概念。
每个事件循环中有一个或多个观察者,而判断是否有事件要处理的过程就是向观察者询问是否需要处理事件。

完美的异步I/O应该是应用程序发起非阻塞调用,无需通过轮询就可以直接处理下一个任务,只需在I/O完成后通过信号或回调将数据传递给应用程序即可。

3.3请求对象

Javascript发起调用到内核执行完I/O操作的过程中,存在一种中间产物,叫做请求对象。
以fs.open()为例:

fs.open = function(path, flags, mode, callback) {
    //...
    binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);
}

fs.open()是根据指定路径和参数打开一个文件,从而获取一个文件描述符,这是后续所有I/O操作的初始操作。
Javascript层面的代码调用C 核心模块进行下层操作。示意图:

图片 12

调用示意图

实际上调用了uv_fs_open()方法。在调用过程中创建了一个FSReqWrap请求对象。从Javascriptc层传入的参数和当前方法都封装在这个请求对象中,回调函数则被设置在对象的oncomplete_sym属性上:

req_wrap->object_->Set(oncomplete_sym, callback);

对象包装完毕,将FSReqWrap对象推入线程池中等待执行。此时Javascript调用立即返回,Javascript线程可继续执行当前任务的后续操作,当前的I/O操作在线程池中等待执行,不管是否是阻塞I/O,的不会影响Javascript线程的后续执行。
请求对象是异步I/O过程的重要中间产物,所有状态都保存在这个对象中,包括送入线程池执行以及I/O操作完毕后的回调处理。

现实中的异步I/O在不同操作系统下有不同的实现,如*nix平台采用自定义的线程池,Windows平台采用IOCP模型。Node提供了libuv作为抽象封装层来封装平台兼容性判断,并保证上层Node与下层各平台异步I/O的实现各自独立。另外需要强调的是我们经常提到Node是单线程的,这仅仅是指Javascript的执行在单线程中,实际在Node内部完成I/O任务的都另有线程池。

3.4执行回调

线程池中的I/O操作调用完毕后,将获取结果储存在req->result属性上,然后通知IOCP(windows下)告知操作已完成,并归还线程到线程池。
在每次Tick的执行中,它会检查线程池中是否有执行完的请求,如果存在,将请求对象加入I/O观察者列队中,然后将其当做事件处理。
I/O观察者回调函数的行为就是取出请求对象的result属性作为参数然后执行回调,调用Javascript中传入的回调函数,至此,这个I/O流程完全接受,示意图:

图片 13

整个异步I/O流程

在Node中除了Javascript是单线程外,Node自身其他都是多线程的,除了用户代码无法并行执行,所有I/O则是可以并行起来的。

2. Node的异步I/O

4.非I/O的异步API

无关I/O的异步API

  • setTimeout()
  • setInterval()
  • setImmediate()
  • process.nextTick()

本文由澳门皇冠金沙网站发布于前端开发,转载请注明出处:O学习笔记